Compare commits
6 Commits
feature-xj
...
mvp-step1
| Author | SHA1 | Date | |
|---|---|---|---|
| ef6cc44d50 | |||
| 1914a8b1d6 | |||
| 2353f414b3 | |||
| e1a6af7e57 | |||
| be05c0879f | |||
| 66d91f01b7 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,7 +1,3 @@
|
|||||||
.vscode
|
.vscode
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
docker-stack.yml
|
docker-stack.yml
|
||||||
backend/pyvenv.cfg
|
|
||||||
backend/Include/
|
|
||||||
backend/Scripts/
|
|
||||||
|
|
||||||
|
|||||||
2
backend/.gitignore
vendored
2
backend/.gitignore
vendored
@@ -56,7 +56,6 @@ coverage.xml
|
|||||||
|
|
||||||
# Django stuff:
|
# Django stuff:
|
||||||
*.log
|
*.log
|
||||||
*.log.*
|
|
||||||
local_settings.py
|
local_settings.py
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
db.sqlite3-journal
|
db.sqlite3-journal
|
||||||
@@ -127,4 +126,3 @@ cython_debug/
|
|||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
*.lock
|
*.lock
|
||||||
Temp/
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
FROM python:3.11.4
|
FROM python:3.8
|
||||||
|
|
||||||
RUN mkdir /app
|
RUN mkdir /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -25,27 +25,22 @@ async def login(
|
|||||||
minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES
|
minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||||
)
|
)
|
||||||
if user.is_superuser:
|
if user.is_superuser:
|
||||||
roles = "super"
|
permissions = "admin"
|
||||||
permissions = "ALL"
|
|
||||||
else:
|
else:
|
||||||
roles = ";".join(role.name for role in user.roles)
|
permissions = "user"
|
||||||
perlst = [perm.privilege for role in user.roles for perm in role.permissions]
|
|
||||||
permissions =";".join(list(set(perlst)))
|
|
||||||
|
|
||||||
access_token = security.create_access_token(
|
access_token = security.create_access_token(
|
||||||
data={"sub": user.id, "roles":roles,"permissions": permissions ,},
|
data={"sub": user.email, "permissions": permissions},
|
||||||
expires_delta=access_token_expires,
|
expires_delta=access_token_expires,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
|
return {"access_token": access_token, "token_type": "bearer"}
|
||||||
|
|
||||||
|
|
||||||
@r.post("/signup")
|
@r.post("/signup")
|
||||||
async def signup(
|
async def signup(
|
||||||
firstname:str, lastname:str,
|
|
||||||
db=Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends()
|
db=Depends(get_db), form_data: OAuth2PasswordRequestForm = Depends()
|
||||||
):
|
):
|
||||||
user = sign_up_new_user(db, form_data.username, form_data.password,firstname,lastname)
|
user = sign_up_new_user(db, form_data.username, form_data.password)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT,
|
status_code=status.HTTP_409_CONFLICT,
|
||||||
@@ -61,8 +56,8 @@ async def signup(
|
|||||||
else:
|
else:
|
||||||
permissions = "user"
|
permissions = "user"
|
||||||
access_token = security.create_access_token(
|
access_token = security.create_access_token(
|
||||||
data={"sub": user.id, "permissions": permissions},
|
data={"sub": user.email, "permissions": permissions},
|
||||||
expires_delta=access_token_expires,
|
expires_delta=access_token_expires,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
|
return {"access_token": access_token, "token_type": "bearer"}
|
||||||
|
|||||||
@@ -5,150 +5,33 @@ import pandas as pd
|
|||||||
import json
|
import json
|
||||||
import httpx
|
import httpx
|
||||||
import deepdiff
|
import deepdiff
|
||||||
import app.core.config as config
|
import app.core.config as c
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
from app.db.session import SessionLocal
|
|
||||||
from app.db.crud import get_flows_by_app,get_activedomain,get_kintoneformat
|
|
||||||
from app.core.auth import get_current_active_user,get_current_user
|
|
||||||
from app.core.apiexception import APIException
|
|
||||||
|
|
||||||
kinton_router = r = APIRouter()
|
kinton_router = r = APIRouter()
|
||||||
|
|
||||||
def getkintoneenv(user = Depends(get_current_user)):
|
def getfieldsfromexcel(df):
|
||||||
db = SessionLocal()
|
|
||||||
domain = get_activedomain(db, user.id)
|
|
||||||
db.close()
|
|
||||||
kintoneevn = config.KINTONE_ENV(domain)
|
|
||||||
return kintoneevn
|
|
||||||
|
|
||||||
|
|
||||||
def getkintoneformat():
|
|
||||||
db = SessionLocal()
|
|
||||||
formats = get_kintoneformat(db)
|
|
||||||
db.close()
|
|
||||||
return formats
|
|
||||||
|
|
||||||
|
|
||||||
def createkintonefields(property,value,trueformat):
|
|
||||||
p = []
|
|
||||||
if(property=="options"):
|
|
||||||
o=[]
|
|
||||||
for v in value.split(','):
|
|
||||||
o.append(f"\"{v.split('|')[0]}\":{{\"label\":\"{v.split('|')[0]}\",\"index\":\"{v.split('|')[1]}\"}}")
|
|
||||||
p.append(f"\"options\":{{{','.join(o)}}}")
|
|
||||||
elif(property =="expression"):
|
|
||||||
p.append(f"\"hideExpression\":true")
|
|
||||||
p.append(f"\"expression\":\"{value.split(':')[1]}\"")
|
|
||||||
elif(property =="required" or property =="unique" or property =="defaultNowValue" or property =="hideExpression" or property =="digit"):
|
|
||||||
if str(value) == trueformat:
|
|
||||||
p.append(f"\"{property}\":true")
|
|
||||||
else:
|
|
||||||
p.append(f"\"{property}\":false")
|
|
||||||
elif(property =="protocol"):
|
|
||||||
if(value == "メールアドレス"):
|
|
||||||
p.append("\"protocol\":\"MAIL\"")
|
|
||||||
elif(value == "Webサイト"):
|
|
||||||
p.append("\"protocol\":\"WEB\"")
|
|
||||||
elif(value == "電話番号"):
|
|
||||||
p.append("\"protocol\":\"CALL\"")
|
|
||||||
else:
|
|
||||||
p.append(f"\"{property}\":\"{value}\"")
|
|
||||||
return p
|
|
||||||
|
|
||||||
def getfieldsfromexcel(df,mapping):
|
|
||||||
startrow = mapping.startrow
|
|
||||||
startcolumn = mapping.startcolumn
|
|
||||||
typecolumn = mapping.typecolumn
|
|
||||||
codecolumn = mapping.codecolumn
|
|
||||||
property = mapping.field.split(",")
|
|
||||||
trueformat = mapping.trueformat
|
|
||||||
appname = df.iloc[0,2]
|
appname = df.iloc[0,2]
|
||||||
col=[]
|
col=[]
|
||||||
for row in range(startrow,len(df)):
|
for row in range(5,len(df)):
|
||||||
if pd.isna(df.iloc[row,startcolumn]):
|
if pd.isna(df.iloc[row,1]):
|
||||||
break
|
break
|
||||||
if not df.iloc[row,typecolumn] in config.KINTONE_FIELD_TYPE:
|
if not df.iloc[row,3] in c.KINTONE_FIELD_TYPE:
|
||||||
continue
|
continue
|
||||||
p=[]
|
p=[]
|
||||||
for column in range(startcolumn,startcolumn + len(property)):
|
for column in range(1,7):
|
||||||
if(not pd.isna(df.iloc[row,column])):
|
if(not pd.isna(df.iloc[row,column])):
|
||||||
propertyname =property[column-1]
|
if(property[column-1]=="options"):
|
||||||
if(propertyname.find("[") == 0):
|
o=[]
|
||||||
continue
|
for v in df.iloc[row,column].split(','):
|
||||||
elif (propertyname =="remark"):
|
o.append(f"\"{v.split('|')[0]}\":{{\"label\":\"{v.split('|')[0]}\",\"index\":\"{v.split('|')[1]}\"}}")
|
||||||
if (df.iloc[row,column].find("|") !=-1):
|
p.append(f"\"{property[column-1]}\":{{{','.join(o)}}}")
|
||||||
propertyname = "options"
|
elif(property[column-1]=="required"):
|
||||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
p.append(f"\"{property[column-1]}\":{df.iloc[row,column]}")
|
||||||
if (df.iloc[row,column] == "メールアドレス" or df.iloc[row,column] == "Webサイト" or df.iloc[row,column] == "電話番号"):
|
|
||||||
propertyname = "protocol"
|
|
||||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
|
||||||
if (df.iloc[row,column].find("桁区切り") !=-1):
|
|
||||||
propertyname = "digit"
|
|
||||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
|
||||||
if (df.iloc[row,column].find("前単位") !=-1):
|
|
||||||
propertyname = "unitPosition"
|
|
||||||
p = p + createkintonefields(propertyname, "BEFORE",trueformat)
|
|
||||||
if (df.iloc[row,column].find("後単位") !=-1):
|
|
||||||
propertyname = "unitPosition"
|
|
||||||
p = p + createkintonefields(propertyname, "AFTER",trueformat)
|
|
||||||
if (df.iloc[row,column].find("単位「") !=-1):
|
|
||||||
propertyname = "unit"
|
|
||||||
ids = df.iloc[row,column].index("単位「")
|
|
||||||
ide = df.iloc[row,column].index("」")
|
|
||||||
unit = df.iloc[row,column][ids+3:ide]
|
|
||||||
p = p + createkintonefields(propertyname, unit,trueformat)
|
|
||||||
else:
|
else:
|
||||||
continue
|
p.append(f"\"{property[column-1]}\":\"{df.iloc[row,column]}\"")
|
||||||
elif(propertyname =="mixValue"):
|
col.append(f"\"{df.iloc[row,2]}\":{{{','.join(p)}}}")
|
||||||
if(df.iloc[row,column].find("レコード登録時の日") != -1):
|
fields = ",".join(col).replace("False","false").replace("True","true")
|
||||||
propertyname = "defaultNowValue"
|
|
||||||
df.iloc[row,column] = trueformat
|
|
||||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
|
||||||
elif(df.iloc[row,column].find("計:") != -1):
|
|
||||||
propertyname = "expression"
|
|
||||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
|
||||||
elif(df.iloc[row,column] !=""):
|
|
||||||
propertyname = "defaultValue"
|
|
||||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
elif(propertyname=="max" or propertyname == "min"):
|
|
||||||
if(df.iloc[row,typecolumn] == "NUMBER"):
|
|
||||||
propertyname = property[column-1] + "Value"
|
|
||||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
|
||||||
else:
|
|
||||||
propertyname = property[column-1] + "Length"
|
|
||||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
|
||||||
else:
|
|
||||||
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
|
||||||
|
|
||||||
# if(propertyname=="options"):
|
|
||||||
# o=[]
|
|
||||||
# for v in df.iloc[row,column].split(','):
|
|
||||||
# o.append(f"\"{v.split('|')[0]}\":{{\"label\":\"{v.split('|')[0]}\",\"index\":\"{v.split('|')[1]}\"}}")
|
|
||||||
# p.append(f"\"options\":{{{','.join(o)}}}")
|
|
||||||
# elif(propertyname=="expression"):
|
|
||||||
# p.append(f"\"hideExpression\":true")
|
|
||||||
# p.append(f"\"expression\":{df.iloc[row,column].split(':')[1]}")
|
|
||||||
# elif(propertyname=="required" or propertyname =="unique" or propertyname=="defaultNowValue" or propertyname=="hideExpression" or propertyname=="digit"):
|
|
||||||
# if (df.iloc[row,column] == trueformat):
|
|
||||||
# p.append(f"\"{propertyname}\":true")
|
|
||||||
# else:
|
|
||||||
# p.append(f"\"{propertyname}\":false")
|
|
||||||
# elif(propertyname =="protocol"):
|
|
||||||
# if(df.iloc[row,column] == "メールアドレス"):
|
|
||||||
# p.append("\"protocol\":\"MAIL\"")
|
|
||||||
# elif(df.iloc[row,column] == "Webサイト"):
|
|
||||||
# p.append("\"protocol\":\"WEB\"")
|
|
||||||
# elif(df.iloc[row,column] == "電話番号"):
|
|
||||||
# p.append("\"protocol\":\"CALL\"")
|
|
||||||
# else:
|
|
||||||
# p.append(f"\"{propertyname}\":\"{df.iloc[row,column]}\"")
|
|
||||||
|
|
||||||
|
|
||||||
col.append(f"\"{df.iloc[row,codecolumn]}\":{{{','.join(p)}}}")
|
|
||||||
fields = ",".join(col).replace("\\", "\\\\")
|
|
||||||
return json.loads(f"{{{fields}}}")
|
return json.loads(f"{{{fields}}}")
|
||||||
|
|
||||||
def getsettingfromexcel(df):
|
def getsettingfromexcel(df):
|
||||||
@@ -156,10 +39,10 @@ def getsettingfromexcel(df):
|
|||||||
des = df.iloc[2,2]
|
des = df.iloc[2,2]
|
||||||
return {"name":appname,"description":des}
|
return {"name":appname,"description":des}
|
||||||
|
|
||||||
def getsettingfromkintone(app:str,env:config.KINTONE_ENV):
|
def getsettingfromkintone(app:str):
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
params = {"app":app}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/settings.json"
|
url = f"{c.BASE_URL}{c.API_V1_STR}/app/settings.json"
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
@@ -171,101 +54,60 @@ def analysesettings(excel,kintone):
|
|||||||
updatesettings[key] = excel[key]
|
updatesettings[key] = excel[key]
|
||||||
return updatesettings
|
return updatesettings
|
||||||
|
|
||||||
def createkintoneapp(name:str,env:config.KINTONE_ENV):
|
def createkintoneapp(name:str):
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
data = {"name":name}
|
data = {"name":name}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app.json"
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def updateappsettingstokintone(app:str,updates:dict,env:config.KINTONE_ENV):
|
def updateappsettingstokintone(app:str,updates:dict):
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/settings.json"
|
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/settings.json"
|
||||||
data = {"app":app}
|
data = {"app":app}
|
||||||
data.update(updates)
|
data.update(updates)
|
||||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def addfieldstokintone(app:str,fields:dict,env:config.KINTONE_ENV,revision:str = None):
|
def addfieldstokintone(app:str,fields:dict,revision:str = None):
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/form/fields.json"
|
||||||
if revision != None:
|
if revision != None:
|
||||||
data = {"app":app,"revision":revision,"properties":fields}
|
data = {"app":app,"revision":revision,"properties":fields}
|
||||||
else:
|
else:
|
||||||
data = {"app":app,"properties":fields}
|
data = {"app":app,"properties":fields}
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
r.raise_for_status()
|
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def updatefieldstokintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV):
|
def updatefieldstokintone(app:str,revision:str,fields:dict):
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/form/fields.json"
|
||||||
data = {"app":app,"properties":fields}
|
data = {"app":app,"properties":fields}
|
||||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def deletefieldsfromkintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV):
|
def deletefieldsfromkintone(app:str,revision:str,fields:dict):
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/form/fields.json"
|
||||||
params = {"app":app,"revision":revision,"fields":fields}
|
params = {"app":app,"revision":revision,"fields":fields}
|
||||||
#r = httpx.delete(url,headers=headers,content=json.dumps(params))
|
#r = httpx.delete(url,headers=headers,content=json.dumps(params))
|
||||||
r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params))
|
r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def deoployappfromkintone(app:str,revision:str,env:config.KINTONE_ENV):
|
def deoployappfromkintone(app:str,revision:str):
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/deploy.json"
|
||||||
data = {"apps":[{"app":app,"revision":revision}],"revert": False}
|
data = {"apps":[{"app":app,"revision":revision}],"revert": False}
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json
|
return r.json
|
||||||
|
|
||||||
# 既定項目に含めるアプリのフィールドのみ取得する
|
def getfieldsfromkintone(app):
|
||||||
# スペース、枠線、ラベルを含まない
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
def getfieldsfromkintone(app:str,env:config.KINTONE_ENV):
|
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
|
||||||
params = {"app":app}
|
params = {"app":app}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/form/fields.json"
|
url = f"{c.BASE_URL}{c.API_V1_STR}/app/form/fields.json"
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
# フォームに配置するフィールドのみ取得する
|
|
||||||
# スペース、枠線、ラベルも含める
|
|
||||||
def getformfromkintone(app:str,env:config.KINTONE_ENV):
|
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
|
||||||
params = {"app":app}
|
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/form.json"
|
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
|
|
||||||
def merge_kintone_fields(fields_response: dict, form_response: dict) -> dict:
|
|
||||||
fields_properties = fields_response.get('properties', {})
|
|
||||||
form_properties = form_response.get('properties', [])
|
|
||||||
|
|
||||||
merged_properties = {k: v for k, v in fields_properties.items()}
|
|
||||||
|
|
||||||
for index, form_field in enumerate(form_properties):
|
|
||||||
code = form_field.get('code')
|
|
||||||
if code:
|
|
||||||
if code and code not in merged_properties:
|
|
||||||
merged_properties[code] = form_field
|
|
||||||
else:
|
|
||||||
element_id = form_field.get('elementId')
|
|
||||||
if element_id:
|
|
||||||
key = element_id
|
|
||||||
form_field['code']=element_id
|
|
||||||
form_field['label']=form_field.get('type')
|
|
||||||
# else:
|
|
||||||
# key = f"{form_field.get('type')}_{index}"
|
|
||||||
merged_properties[key] = form_field
|
|
||||||
|
|
||||||
merged_response = {
|
|
||||||
'revision': fields_response.get('revision', ''),
|
|
||||||
'properties': merged_properties
|
|
||||||
}
|
|
||||||
|
|
||||||
return merged_response
|
|
||||||
|
|
||||||
def analysefields(excel,kintone):
|
def analysefields(excel,kintone):
|
||||||
updatefields={}
|
updatefields={}
|
||||||
addfields={}
|
addfields={}
|
||||||
@@ -274,22 +116,22 @@ def analysefields(excel,kintone):
|
|||||||
adds = excel.keys() - kintone.keys()
|
adds = excel.keys() - kintone.keys()
|
||||||
dels = kintone.keys() - excel.keys()
|
dels = kintone.keys() - excel.keys()
|
||||||
for key in updates:
|
for key in updates:
|
||||||
for p in config.KINTONE_FIELD_PROPERTY:
|
for p in property:
|
||||||
if excel[key].get(p) != None and kintone[key].get(p) != None and kintone[key][p] != excel[key][p]:
|
if excel[key].get(p) != None and kintone[key][p] != excel[key][p]:
|
||||||
updatefields[key] = excel[key]
|
updatefields[key] = excel[key]
|
||||||
break
|
break
|
||||||
for key in adds:
|
for key in adds:
|
||||||
addfields[key] = excel[key]
|
addfields[key] = excel[key]
|
||||||
for key in dels:
|
for key in dels:
|
||||||
if kintone[key]["type"] in config.KINTONE_FIELD_TYPE:
|
if kintone[key]["type"] in c.KINTONE_FIELD_TYPE:
|
||||||
delfields.append(key)
|
delfields.append(key)
|
||||||
|
|
||||||
return {"update":updatefields,"add":addfields,"del":delfields}
|
return {"update":updatefields,"add":addfields,"del":delfields}
|
||||||
|
|
||||||
def getprocessfromkintone(app:str,env:config.KINTONE_ENV):
|
def getprocessfromkintone(app:str):
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
params = {"app":app}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/status.json"
|
url = f"{c.BASE_URL}{c.API_V1_STR}/app/status.json"
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
@@ -353,121 +195,27 @@ def analysprocess(excel,kintone):
|
|||||||
# return True
|
# return True
|
||||||
return diff
|
return diff
|
||||||
|
|
||||||
def updateprocesstokintone(app:str,process:dict,c:config.KINTONE_ENV):
|
def updateprocesstokintone(app:str,process:dict):
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/status.json"
|
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/status.json"
|
||||||
data = {"app":app,"enable":True}
|
data = {"app":app,"enable":True}
|
||||||
data.update(process)
|
data.update(process)
|
||||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def getkintoneusers(c:config.KINTONE_ENV):
|
def getkintoneusers():
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
url = f"{c.BASE_URL}/v1/users.json"
|
url = f"{c.BASE_URL}/v1/users.json"
|
||||||
r = httpx.get(url,headers=headers)
|
r = httpx.get(url,headers=headers)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def getkintoneorgs(c:config.KINTONE_ENV):
|
def getkintoneorgs():
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
params = {"code":c.KINTONE_USER}
|
params = {"code":c.KINTONE_USER}
|
||||||
url = f"{c.BASE_URL}/v1/user/organizations.json"
|
url = f"{c.BASE_URL}/v1/user/organizations.json"
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def uploadkintonefiles(file,env:config.KINTONE_ENV):
|
|
||||||
if (file.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
|
||||||
return {'fileKey':file}
|
|
||||||
upload_files = {'file': open(file,'rb')}
|
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
|
||||||
data ={'name':'file','filename':os.path.basename(file)}
|
|
||||||
url = f"{env.BASE_URL}/k/v1/file.json"
|
|
||||||
r = httpx.post(url,headers=headers,data=data,files=upload_files)
|
|
||||||
#{"name":data['filename'],'fileKey':r['fileKey']}
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
def updateappjscss(app,uploads,env:config.KINTONE_ENV):
|
|
||||||
dsjs = []
|
|
||||||
dscss = []
|
|
||||||
#mobile側
|
|
||||||
mbjs = []
|
|
||||||
mbcss = []
|
|
||||||
customize = getappcustomize(app, env)
|
|
||||||
current_js = customize['desktop'].get('js', [])
|
|
||||||
current_css = customize['desktop'].get('css', [])
|
|
||||||
current_mobile_js = customize['mobile'].get('js', [])
|
|
||||||
current_mobile_css = customize['mobile'].get('css', [])
|
|
||||||
current_js = [item for item in current_js if not (item.get('type') == 'URL' and item.get('url', '').endswith('alc_runtime.js'))]
|
|
||||||
for upload in uploads:
|
|
||||||
for key in upload:
|
|
||||||
filename = os.path.basename(key)
|
|
||||||
if key.endswith('.js'):
|
|
||||||
existing_js = next((item for item in current_js
|
|
||||||
if item.get('type') == 'FILE' and item['file']['name'] == filename
|
|
||||||
), None)
|
|
||||||
if existing_js:
|
|
||||||
current_js = [item for item in current_js if item.get('type') == 'URL' or item['file'].get('name') != filename]
|
|
||||||
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
|
||||||
else:
|
|
||||||
if (key.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
|
||||||
dsjs.append({'type':'URL','url':config.DEPLOY_JS_URL})
|
|
||||||
else:
|
|
||||||
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
|
||||||
elif key.endswith('.css'):
|
|
||||||
existing_css = next((item for item in current_css
|
|
||||||
if item.get('type') == 'FILE' and item['file']['name'] == filename
|
|
||||||
), None)
|
|
||||||
if existing_css:
|
|
||||||
current_css = [item for item in current_css if item.get('type') == 'URL' or item['file'].get('name') != filename]
|
|
||||||
dscss.append({'type': 'FILE', 'file': {'fileKey': upload[key]}})
|
|
||||||
else:
|
|
||||||
dscss.append({'type': 'FILE', 'file': {'fileKey': upload[key]}})
|
|
||||||
#現在のJSとCSSがdsjsに追加する
|
|
||||||
dsjs.extend(current_js)
|
|
||||||
dscss.extend(current_css)
|
|
||||||
mbjs.extend(current_mobile_js)
|
|
||||||
mbcss.extend(current_mobile_css)
|
|
||||||
|
|
||||||
ds ={'js':dsjs,'css':dscss}
|
|
||||||
mb ={'js':mbjs,'css':mbcss}
|
|
||||||
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb,'revision':customize["revision"]}
|
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
|
||||||
print(json.dumps(data))
|
|
||||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
#kintone カスタマイズ情報
|
|
||||||
def getappcustomize(app,env:config.KINTONE_ENV):
|
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
|
||||||
params = {"app":app}
|
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
|
||||||
return r.json()
|
|
||||||
|
|
||||||
|
|
||||||
def getTempPath(filename):
|
|
||||||
scriptdir = Path(__file__).resolve().parent
|
|
||||||
rootdir = scriptdir.parent.parent.parent.parent
|
|
||||||
fpath = os.path.join(rootdir,"Temp",filename)
|
|
||||||
return fpath
|
|
||||||
|
|
||||||
def createappjs(domain_url,app):
|
|
||||||
db = SessionLocal()
|
|
||||||
flows = get_flows_by_app(db,domain_url,app)
|
|
||||||
db.close()
|
|
||||||
content={}
|
|
||||||
for flow in flows:
|
|
||||||
content[flow.eventid] = {'flowid':flow.flowid,'name':flow.name,'content':flow.content}
|
|
||||||
js = 'const alcflow=' + json.dumps(content)
|
|
||||||
# scriptdir = Path(__file__).resolve().parent
|
|
||||||
# rootdir = scriptdir.parent.parent.parent.parent
|
|
||||||
# fpath = os.path.join(rootdir,"Temp",f"alc_setting_{app}.js")
|
|
||||||
fpath = getTempPath(f"alc_setting_{app}.js")
|
|
||||||
print(fpath)
|
|
||||||
with open(fpath,'w') as file:
|
|
||||||
file.write(js)
|
|
||||||
return fpath
|
|
||||||
|
|
||||||
@r.post("/test",)
|
@r.post("/test",)
|
||||||
async def test(file:UploadFile= File(...),app:str=None):
|
async def test(file:UploadFile= File(...),app:str=None):
|
||||||
if file.filename.endswith('.xlsx'):
|
if file.filename.endswith('.xlsx'):
|
||||||
@@ -485,26 +233,15 @@ async def test(file:UploadFile= File(...),app:str=None):
|
|||||||
# kintone = getfieldsfromkintone(app)
|
# kintone = getfieldsfromkintone(app)
|
||||||
# fields = analysefields(excel,kintone["properties"])
|
# fields = analysefields(excel,kintone["properties"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}")
|
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}")
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
|
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
|
||||||
|
|
||||||
return test
|
return test
|
||||||
|
|
||||||
|
|
||||||
@r.post("/download",)
|
@r.post("/upload",)
|
||||||
async def download(request:Request,key,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
async def upload(files:t.List[UploadFile] = File(...)):
|
||||||
try:
|
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
|
||||||
params = {"fileKey":key}
|
|
||||||
url = f"{c.BASE_URL}/k/v1/file.json"
|
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
|
||||||
return r.json()
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('kintone:upload',request.url._url,f"Error occurred while download file.json:",e)
|
|
||||||
|
|
||||||
@r.post("/upload")
|
|
||||||
async def upload(request:Request,files:t.List[UploadFile] = File(...)):
|
|
||||||
dataframes = []
|
dataframes = []
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.filename.endswith('.xlsx'):
|
if file.filename.endswith('.xlsx'):
|
||||||
@@ -514,124 +251,61 @@ async def upload(request:Request,files:t.List[UploadFile] = File(...)):
|
|||||||
print(df)
|
print(df)
|
||||||
dataframes.append(df)
|
dataframes.append(df)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('kintone:upload',request.url._url,f"Error occurred while uploading file {file.filename}:",e)
|
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}")
|
||||||
else:
|
else:
|
||||||
raise APIException('kintone:upload',request.url._url, f"File {file.filename} is not an Excel file",e)
|
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
|
||||||
|
|
||||||
return {"files": [file.filename for file in files]}
|
return {"files": [file.filename for file in files]}
|
||||||
|
|
||||||
@r.post("/updatejscss")
|
@r.get("/allapps",)
|
||||||
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
async def allapps():
|
||||||
try:
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
jscs=[]
|
url = f"{c.BASE_URL}{c.API_V1_STR}/apps.json"
|
||||||
for file in files:
|
r = httpx.get(url,headers=headers)
|
||||||
fbytes = file.file.read()
|
return r.json()
|
||||||
fname = file.filename
|
|
||||||
fpath = '{}\\{}'.format('Temp',fname)
|
|
||||||
fout = open(fpath,'wb')
|
|
||||||
fout.write(fbytes)
|
|
||||||
fout.close()
|
|
||||||
upload = uploadkintonefiles(fpath,env)
|
|
||||||
if upload.get('fileKey') != None:
|
|
||||||
jscs.append({ file.filename:upload['fileKey']})
|
|
||||||
appjscs = updateappjscss(app,jscs,env)
|
|
||||||
if appjscs.get("revision") != None:
|
|
||||||
deoployappfromkintone(app,appjscs["revision"],env)
|
|
||||||
return appjscs
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('kintone:updatejscss',request.url._url, f"Error occurred while update js/css {file.filename} is not an Excel file",e)
|
|
||||||
|
|
||||||
@r.get("/app")
|
@r.get("/app")
|
||||||
async def app(request:Request,app:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
async def app(app:str):
|
||||||
try:
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
url = f"{c.BASE_URL}{c.API_V1_STR}/app.json"
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app.json"
|
|
||||||
params ={"id":app}
|
params ={"id":app}
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
except Exception as e:
|
|
||||||
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({env.DOMAIN_NAME}->{app}):",e)
|
|
||||||
|
|
||||||
@r.get("/allapps")
|
|
||||||
async def allapps(request:Request,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
|
||||||
try:
|
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/apps.json"
|
|
||||||
offset = 0
|
|
||||||
limit = 100
|
|
||||||
all_apps = []
|
|
||||||
|
|
||||||
while True:
|
|
||||||
r = httpx.get(f"{url}?limit={limit}&offset={offset}", headers=headers)
|
|
||||||
json_data = r.json()
|
|
||||||
apps = json_data.get("apps",[])
|
|
||||||
all_apps.extend(apps)
|
|
||||||
if len(apps)<limit:
|
|
||||||
break
|
|
||||||
offset += limit
|
|
||||||
return {"apps": all_apps}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('kintone:allapps', request.url._url, f"Error occurred while get allapps({env.DOMAIN_NAME}):", e)
|
|
||||||
|
|
||||||
@r.get("/appfields")
|
@r.get("/appfields")
|
||||||
async def appfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
async def appfields(app:str):
|
||||||
try:
|
return getfieldsfromkintone(app)
|
||||||
return getfieldsfromkintone(app,env)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAME}->{app}):",e)
|
|
||||||
|
|
||||||
@r.get("/allfields")
|
|
||||||
async def allfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
|
||||||
try:
|
|
||||||
field_resp = getfieldsfromkintone(app,env)
|
|
||||||
form_resp = getformfromkintone(app,env)
|
|
||||||
return merge_kintone_fields(field_resp,form_resp)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('kintone:allfields',request.url._url, f"Error occurred while get form fileds({env.DOMAIN_NAME}->{app}):",e)
|
|
||||||
|
|
||||||
@r.get("/appprocess")
|
@r.get("/appprocess")
|
||||||
async def appprocess(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
async def appprocess(app:str):
|
||||||
try:
|
return getprocessfromkintone(app)
|
||||||
return getprocessfromkintone(app,env)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAME}->{app}):",e)
|
|
||||||
|
|
||||||
@r.get("/alljscss")
|
@r.get("/alljscs")
|
||||||
async def alljscs(request:Request,app:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
async def alljscs(app:str):
|
||||||
try:
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
url = f"{c.BASE_URL}{c.API_V1_STR}/app/customize.json"
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/customize.json"
|
|
||||||
params = {"app":app}
|
params = {"app":app}
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
except Exception as e:
|
|
||||||
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({env.DOMAIN_NAME}->{app}):",e)
|
|
||||||
|
|
||||||
@r.post("/createapp",)
|
@r.post("/createapp",)
|
||||||
async def createapp(request:Request,name:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
async def createapp(name:str):
|
||||||
try:
|
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
|
||||||
data = {"name":name}
|
data = {"name":name}
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app.json"
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
result = r.json()
|
result = r.json()
|
||||||
if result.get("app") != None:
|
if result.get("app") != None:
|
||||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/deploy.json"
|
||||||
data = {"apps":[result],"revert": False}
|
data = {"apps":[result],"revert": False}
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json
|
return r.json
|
||||||
except Exception as e:
|
|
||||||
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({env.DOMAIN_NAME}->{name}):",e)
|
|
||||||
|
|
||||||
|
property=["label","code","type","required","defaultValue","options"]
|
||||||
|
|
||||||
@r.post("/createappfromexcel",)
|
@r.post("/createappfromexcel",)
|
||||||
async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
|
async def createappfromexcel(files:t.List[UploadFile] = File(...)):
|
||||||
try:
|
|
||||||
mapping = getkintoneformat()[format]
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('kintone:createappfromexcel',request.url._url, f"Error occurred while get kintone format:",e)
|
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.filename.endswith('.xlsx'):
|
if file.filename.endswith('.xlsx'):
|
||||||
try:
|
try:
|
||||||
@@ -641,90 +315,87 @@ async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...
|
|||||||
appname = df.iloc[0,2]
|
appname = df.iloc[0,2]
|
||||||
desc = df.iloc[2,2]
|
desc = df.iloc[2,2]
|
||||||
result = {"app":0,"revision":0,"msg":""}
|
result = {"app":0,"revision":0,"msg":""}
|
||||||
fields = getfieldsfromexcel(df,mapping)
|
fields = getfieldsfromexcel(df)
|
||||||
users = getkintoneusers(env)
|
users = getkintoneusers()
|
||||||
orgs = getkintoneorgs(env)
|
orgs = getkintoneorgs()
|
||||||
processes = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
|
processes = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
|
||||||
app = createkintoneapp(appname,env)
|
app = createkintoneapp(appname)
|
||||||
if app.get("app") != None:
|
if app.get("app") != None:
|
||||||
result["app"] = app["app"]
|
result["app"] = app["app"]
|
||||||
app = updateappsettingstokintone(result["app"],{"description":desc},env)
|
app = updateappsettingstokintone(result["app"],{"description":desc})
|
||||||
if app.get("revision") != None:
|
if app.get("revision") != None:
|
||||||
result["revision"] = app["revision"]
|
result["revision"] = app["revision"]
|
||||||
app = addfieldstokintone(result["app"],fields,env)
|
app = addfieldstokintone(result["app"],fields)
|
||||||
if len(processes)> 0:
|
if len(processes)> 0:
|
||||||
app = updateprocesstokintone(result["app"],processes,env)
|
app = updateprocesstokintone(result["app"],processes)
|
||||||
if app.get("revision") != None:
|
if app.get("revision") != None:
|
||||||
result["revision"] = app["revision"]
|
result["revision"] = app["revision"]
|
||||||
deoployappfromkintone(result["app"],result["revision"],env)
|
deoployappfromkintone(result["app"],result["revision"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('kintone:createappfromexcel',request.url._url, f"Error occurred while parsing file ({env.DOMAIN_NAME}->{file.filename}):",e)
|
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}")
|
||||||
else:
|
else:
|
||||||
raise APIException('kintone:createappfromexcel',request.url._url, f"File {file.filename} is not an Excel file",e)
|
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@r.post("/updateappfromexcel")
|
@r.post("/updateappfromexcel",)
|
||||||
async def updateappfromexcel(request:Request,app:str,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
|
async def updateappfromexcel(app:str,files:t.List[UploadFile] = File(...)):
|
||||||
try:
|
|
||||||
mapping = getkintoneformat()[format]
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('kintone:updateappfromexcel',request.url._url, f"Error occurred while get kintone format:",e)
|
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
if file.filename.endswith('.xlsx'):
|
if file.filename.endswith('.xlsx'):
|
||||||
try:
|
try:
|
||||||
content = await file.read()
|
content = await file.read()
|
||||||
df = pd.read_excel(BytesIO(content))
|
df = pd.read_excel(BytesIO(content))
|
||||||
excel = getsettingfromexcel(df)
|
excel = getsettingfromexcel(df)
|
||||||
kintone= getsettingfromkintone(app,env)
|
kintone= getsettingfromkintone(app)
|
||||||
settings = analysesettings(excel,kintone)
|
settings = analysesettings(excel,kintone)
|
||||||
excel = getfieldsfromexcel(df,mapping)
|
excel = getfieldsfromexcel(df)
|
||||||
kintone = getfieldsfromkintone(app,env)
|
kintone = getfieldsfromkintone(app)
|
||||||
users = getkintoneusers(env)
|
users = getkintoneusers()
|
||||||
orgs = getkintoneorgs(env)
|
orgs = getkintoneorgs()
|
||||||
exp = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
|
exp = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
|
||||||
#exp = getprocessfromexcel(df)
|
#exp = getprocessfromexcel(df)
|
||||||
kinp = getprocessfromkintone(app,env)
|
kinp = getprocessfromkintone(app)
|
||||||
process = analysprocess(exp,kinp)
|
process = analysprocess(exp,kinp)
|
||||||
revision = kintone["revision"]
|
revision = kintone["revision"]
|
||||||
fields = analysefields(excel,kintone["properties"])
|
fields = analysefields(excel,kintone["properties"])
|
||||||
result = {"app":app,"revision":revision,"msg":"No Update"}
|
result = {"app":app,"revision":revision,"msg":"No Update"}
|
||||||
deploy = False
|
deploy = False
|
||||||
if len(fields["update"]) > 0:
|
if len(fields["update"]) > 0:
|
||||||
result = updatefieldstokintone(app,revision,fields["update"],env)
|
result = updatefieldstokintone(app,revision,fields["update"])
|
||||||
revision = result["revision"]
|
revision = result["revision"]
|
||||||
deploy = True
|
deploy = True
|
||||||
if len(fields["add"]) > 0:
|
if len(fields["add"]) > 0:
|
||||||
result = addfieldstokintone(app,fields["add"],env,revision)
|
result = addfieldstokintone(app,fields["add"],revision)
|
||||||
revision = result["revision"]
|
revision = result["revision"]
|
||||||
deploy = True
|
deploy = True
|
||||||
if len(fields["del"]) > 0:
|
if len(fields["del"]) > 0:
|
||||||
result = deletefieldsfromkintone(app,revision,fields["del"],env)
|
result = deletefieldsfromkintone(app,revision,fields["del"])
|
||||||
revision = result["revision"]
|
revision = result["revision"]
|
||||||
deploy = True
|
deploy = True
|
||||||
if len(settings) > 0:
|
if len(settings) > 0:
|
||||||
result = updateappsettingstokintone(app,settings,env)
|
result = updateappsettingstokintone(app,settings)
|
||||||
revision = result["revision"]
|
revision = result["revision"]
|
||||||
deploy = True
|
deploy = True
|
||||||
if len(process)>0:
|
if len(process)>0:
|
||||||
result = updateprocesstokintone(app,exp,env)
|
result = updateprocesstokintone(app,exp)
|
||||||
revision = result["revision"]
|
revision = result["revision"]
|
||||||
deploy = True
|
deploy = True
|
||||||
if deploy:
|
if deploy:
|
||||||
result = deoployappfromkintone(app,revision,env)
|
result = deoployappfromkintone(app,revision)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('kintone:updateappfromexcel',request.url._url, f"Error occurred while parsing file ({env.DOMAIN_NAME}->{file.filename}):",e)
|
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}")
|
||||||
else:
|
else:
|
||||||
raise APIException('kintone:updateappfromexcel',request.url._url, f"File {file.filename} is not an Excel file",e)
|
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@r.post("/updateprocessfromexcel",)
|
@r.post("/updateprocessfromexcel",)
|
||||||
async def updateprocessfromexcel(request:Request,app:str,env = Depends(getkintoneenv)):
|
async def updateprocessfromexcel(app:str):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
excel = getprocessfromexcel()
|
excel = getprocessfromexcel()
|
||||||
kintone = getprocessfromkintone(app,env)
|
kintone = getprocessfromkintone(app)
|
||||||
revision = kintone["revision"]
|
revision = kintone["revision"]
|
||||||
#fields = analysefields(excel,kintone["properties"])
|
#fields = analysefields(excel,kintone["properties"])
|
||||||
result = {"app":app,"revision":revision,"msg":"No Update"}
|
result = {"app":app,"revision":revision,"msg":"No Update"}
|
||||||
@@ -745,33 +416,14 @@ async def updateprocessfromexcel(request:Request,app:str,env = Depends(getkinton
|
|||||||
# result = updateappsettingstokintone(app,settings)
|
# result = updateappsettingstokintone(app,settings)
|
||||||
# revision = result["revision"]
|
# revision = result["revision"]
|
||||||
# deploy = True
|
# deploy = True
|
||||||
result = updateprocesstokintone(app,excel,env)
|
result = updateprocesstokintone(app,excel)
|
||||||
revision = result["revision"]
|
revision = result["revision"]
|
||||||
deploy = True
|
deploy = True
|
||||||
if deploy:
|
if deploy:
|
||||||
result = deoployappfromkintone(app,revision,env)
|
result = deoployappfromkintone(app,revision)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('kintone:updateprocessfromexcel',request.url._url, f"Error occurred while update process ({env.DOMAIN_NAME}->{app}):",e)
|
raise HTTPException(status_code=400, detail=f"Error occurred : {str(e)}")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@r.post("/createjstokintone",)
|
|
||||||
async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
|
||||||
try:
|
|
||||||
jscs=[]
|
|
||||||
files=[]
|
|
||||||
files.append(createappjs(env.BASE_URL, app))
|
|
||||||
files.append(getTempPath('alc_runtime.js'))
|
|
||||||
files.append(getTempPath('alc_runtime.css'))
|
|
||||||
for file in files:
|
|
||||||
upload = uploadkintonefiles(file,env)
|
|
||||||
if upload.get('fileKey') != None:
|
|
||||||
print(upload)
|
|
||||||
jscs.append({ file :upload['fileKey']})
|
|
||||||
appjscs = updateappjscss(app,jscs,env)
|
|
||||||
if appjscs.get("revision") != None:
|
|
||||||
deoployappfromkintone(app,appjscs["revision"],env)
|
|
||||||
return appjscs
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('kintone:createjstokintone',request.url._url, f"Error occurred while create js ({env.DOMAIN_NAME}->{app}):",e)
|
|
||||||
|
|||||||
@@ -1,88 +1,11 @@
|
|||||||
from http import HTTPStatus
|
from fastapi import Request,Depends, APIRouter, UploadFile,HTTPException,File
|
||||||
from fastapi import Query, Request,Depends, APIRouter, UploadFile,HTTPException,File
|
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
# from app.core.operation import log_operation
|
|
||||||
from app.db import Base,engine
|
from app.db import Base,engine
|
||||||
from app.db.session import get_db
|
from app.db.session import get_db
|
||||||
from app.db.crud import *
|
from app.db.crud import *
|
||||||
from app.db.schemas import *
|
from app.db.schemas import AppBase, AppEdit, App,Kintone
|
||||||
from typing import List, Optional
|
|
||||||
from app.core.auth import get_current_active_user,get_current_user
|
|
||||||
from app.core.apiexception import APIException
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
import app.core.config as config
|
|
||||||
|
|
||||||
platform_router = r = APIRouter()
|
platform_router = r = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
|
||||||
"/apps",
|
|
||||||
response_model=List[AppList],
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def apps_list(
|
|
||||||
request: Request,
|
|
||||||
user = Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
|
|
||||||
domain = get_activedomain(db, user.id)
|
|
||||||
platformapps = get_apps(db,domain.url)
|
|
||||||
kintoneevn = config.KINTONE_ENV(domain)
|
|
||||||
headers={config.API_V1_AUTH_KEY:kintoneevn.API_V1_AUTH_VALUE}
|
|
||||||
url = f"{kintoneevn.BASE_URL}{config.API_V1_STR}/apps.json"
|
|
||||||
offset = 0
|
|
||||||
limit = 100
|
|
||||||
all_apps = []
|
|
||||||
|
|
||||||
while True:
|
|
||||||
r = httpx.get(f"{url}?limit={limit}&offset={offset}", headers=headers)
|
|
||||||
json_data = r.json()
|
|
||||||
apps = json_data.get("apps",[])
|
|
||||||
all_apps.extend(apps)
|
|
||||||
if len(apps)<limit:
|
|
||||||
break
|
|
||||||
offset += limit
|
|
||||||
|
|
||||||
kintone_apps_dict = {app['appId']: app for app in all_apps}
|
|
||||||
filtered_apps = []
|
|
||||||
for papp in platformapps:
|
|
||||||
if papp.appid in kintone_apps_dict:
|
|
||||||
papp.appname = kintone_apps_dict[papp.appid]["name"]
|
|
||||||
filtered_apps.append(papp)
|
|
||||||
return filtered_apps
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:apps',request.url._url,f"Error occurred while get apps:",e)
|
|
||||||
|
|
||||||
@r.post("/apps", response_model=AppList, response_model_exclude_none=True)
|
|
||||||
async def apps_update(
|
|
||||||
request: Request,
|
|
||||||
app: AppVersion,
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
return update_appversion(db, app,user.id)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:apps',request.url._url,f"Error occurred while get create app :",e)
|
|
||||||
|
|
||||||
@r.delete(
|
|
||||||
"/apps/{domainurl}/{appid}", response_model_exclude_none=True
|
|
||||||
)
|
|
||||||
async def apps_delete(
|
|
||||||
request: Request,
|
|
||||||
domainurl:str,
|
|
||||||
appid: str,
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
return delete_apps(db, domainurl,appid)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:apps',request.url._url,f"Error occurred while delete apps({domainurl}:{appid}):",e)
|
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/appsettings/{id}",
|
"/appsettings/{id}",
|
||||||
response_model=App,
|
response_model=App,
|
||||||
@@ -93,11 +16,9 @@ async def appsetting_details(
|
|||||||
id: int,
|
id: int,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
|
||||||
app = get_appsetting(db, id)
|
app = get_appsetting(db, id)
|
||||||
return app
|
return app
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:appsettings',request.url._url,f"Error occurred while get app setting:",e)
|
|
||||||
|
|
||||||
@r.post("/appsettings", response_model=App, response_model_exclude_none=True)
|
@r.post("/appsettings", response_model=App, response_model_exclude_none=True)
|
||||||
async def appsetting_create(
|
async def appsetting_create(
|
||||||
@@ -105,10 +26,7 @@ async def appsetting_create(
|
|||||||
app: AppBase,
|
app: AppBase,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
|
||||||
return create_appsetting(db, app)
|
return create_appsetting(db, app)
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:appsettings',request.url._url,f"Error occurred while get create app setting:",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.put(
|
@r.put(
|
||||||
@@ -120,10 +38,7 @@ async def appsetting_edit(
|
|||||||
app: AppEdit,
|
app: AppEdit,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
|
||||||
return edit_appsetting(db, id, app)
|
return edit_appsetting(db, id, app)
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:appsettings',request.url._url,f"Error occurred while edit app setting:",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.delete(
|
@r.delete(
|
||||||
@@ -134,10 +49,8 @@ async def appsettings_delete(
|
|||||||
id: int,
|
id: int,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
|
||||||
return delete_appsetting(db, id)
|
return delete_appsetting(db, id)
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:appsettings',request.url._url,f"Error occurred while delete app setting:",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
@@ -150,281 +63,5 @@ async def kintone_data(
|
|||||||
type: int,
|
type: int,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
|
||||||
kintone = get_kintones(db, type)
|
kintone = get_kintones(db, type)
|
||||||
return kintone
|
return kintone
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:kintone',request.url._url,f"Error occurred while get kintone env:",e)
|
|
||||||
|
|
||||||
@r.get(
|
|
||||||
"/actions",
|
|
||||||
response_model=t.List[Action],
|
|
||||||
response_model_exclude={"id"},
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def action_data(
|
|
||||||
request: Request,
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
actions = get_actions(db)
|
|
||||||
return actions
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:actions',request.url._url,f"Error occurred while get actions:",e)
|
|
||||||
|
|
||||||
@r.get(
|
|
||||||
"/flow/{flowid}",
|
|
||||||
response_model=Flow,
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def flow_details(
|
|
||||||
request: Request,
|
|
||||||
flowid: str,
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
app = get_flow(db, flowid)
|
|
||||||
return app
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by flowid:",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
|
||||||
"/flows/{appid}",
|
|
||||||
response_model=List[Flow],
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def flow_list(
|
|
||||||
request: Request,
|
|
||||||
appid: str,
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
domain = get_activedomain(db, user.id)
|
|
||||||
print("domain=>",domain)
|
|
||||||
flows = get_flows_by_app(db, domain.url, appid)
|
|
||||||
return flows
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by appid:",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.post("/flow", response_model=Flow, response_model_exclude_none=True)
|
|
||||||
async def flow_create(
|
|
||||||
request: Request,
|
|
||||||
flow: FlowIn,
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
domain = get_activedomain(db, user.id)
|
|
||||||
return create_flow(db, domain.url, flow)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.put(
|
|
||||||
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
|
|
||||||
)
|
|
||||||
async def flow_edit(
|
|
||||||
request: Request,
|
|
||||||
flowid: str,
|
|
||||||
flow: FlowIn,
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
domain = get_activedomain(db, user.id)
|
|
||||||
return edit_flow(db,domain.url, flow,user.id)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.delete(
|
|
||||||
"/flow/{flowid}", response_model=Flow, response_model_exclude_none=True
|
|
||||||
)
|
|
||||||
async def flow_delete(
|
|
||||||
request: Request,
|
|
||||||
flowid: str,
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
return delete_flow(db, flowid)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while delete flow:",e)
|
|
||||||
|
|
||||||
@r.get(
|
|
||||||
"/domains/{tenantid}",
|
|
||||||
response_model=List[Domain],
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def domain_details(
|
|
||||||
request: Request,
|
|
||||||
tenantid:str,
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
domains = get_domains(db,tenantid)
|
|
||||||
return domains
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:domains',request.url._url,f"Error occurred while get domains:",e)
|
|
||||||
|
|
||||||
@r.post("/domain", response_model=Domain, response_model_exclude_none=True)
|
|
||||||
async def domain_create(
|
|
||||||
request: Request,
|
|
||||||
domain: DomainBase,
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
return create_domain(db, domain,user.id)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.put(
|
|
||||||
"/domain", response_model=Domain, response_model_exclude_none=True
|
|
||||||
)
|
|
||||||
async def domain_edit(
|
|
||||||
request: Request,
|
|
||||||
domain: DomainBase,
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
return edit_domain(db, domain,user.id)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.delete(
|
|
||||||
"/domain/{id}", response_model=Domain, response_model_exclude_none=True
|
|
||||||
)
|
|
||||||
async def domain_delete(
|
|
||||||
request: Request,
|
|
||||||
id: int,
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
return delete_domain(db,id)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while delete domain:",e)
|
|
||||||
|
|
||||||
@r.get(
|
|
||||||
"/domain",
|
|
||||||
# response_model=List[Domain],
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def userdomain_details(
|
|
||||||
request: Request,
|
|
||||||
userId: Optional[int] = Query(None, alias="userId"),
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
domains = get_domain(db, userId if userId is not None else user.id)
|
|
||||||
return domains
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while get user({user.id}) domain:",e)
|
|
||||||
|
|
||||||
@r.post(
|
|
||||||
"/domain/{userid}",
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def create_userdomain(
|
|
||||||
request: Request,
|
|
||||||
userid: int,
|
|
||||||
domainids:List[int] ,
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
domain = add_userdomain(db, userid,domainids)
|
|
||||||
return domain
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while add user({userid}) domain:",e)
|
|
||||||
|
|
||||||
@r.delete(
|
|
||||||
"/domain/{domainid}/{userid}", response_model_exclude_none=True
|
|
||||||
)
|
|
||||||
async def userdomain_delete(
|
|
||||||
request: Request,
|
|
||||||
domainid:int,
|
|
||||||
userid: int,
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
return delete_userdomain(db, userid,domainid)
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:delete',request.url._url,f"Error occurred while delete user({userid}) domain:",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
|
||||||
"/activedomain",
|
|
||||||
response_model=Domain,
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def get_useractivedomain(
|
|
||||||
request: Request,
|
|
||||||
userId: Optional[int] = Query(None, alias="userId"),
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
# domain = get_activedomain(db, user.id)
|
|
||||||
domain = get_activedomain(db, userId if userId is not None else user.id)
|
|
||||||
if domain is None:
|
|
||||||
return JSONResponse(content=None,status_code=HTTPStatus.OK)
|
|
||||||
return domain
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:activedomain',request.url._url,f"Error occurred while get user({user.id}) activedomain:",e)
|
|
||||||
|
|
||||||
@r.put(
|
|
||||||
"/activedomain/{domainid}",
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def update_activeuserdomain(
|
|
||||||
request: Request,
|
|
||||||
domainid:int,
|
|
||||||
userId: Optional[int] = Query(None, alias="userId"),
|
|
||||||
user=Depends(get_current_active_user),
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
domain = active_userdomain(db, userId if userId is not None else user.id,domainid)
|
|
||||||
return domain
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:activedomain',request.url._url,f"Error occurred while update user({user.id}) activedomain:",e)
|
|
||||||
|
|
||||||
@r.get(
|
|
||||||
"/events",
|
|
||||||
response_model=t.List[Event],
|
|
||||||
response_model_exclude={"id"},
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def event_data(
|
|
||||||
request: Request,
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
events = get_events(db)
|
|
||||||
return events
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:events',request.url._url,f"Error occurred while get events:",e)
|
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
|
||||||
"/eventactions/{eventid}",
|
|
||||||
response_model=t.List[Action],
|
|
||||||
response_model_exclude={"id"},
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def eventactions_data(
|
|
||||||
request: Request,
|
|
||||||
eventid: str,
|
|
||||||
db=Depends(get_db),
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
actions = get_eventactions(db,eventid)
|
|
||||||
return actions
|
|
||||||
except Exception as e:
|
|
||||||
raise APIException('platform:eventactions',request.url._url,f"Error occurred while get eventactions:",e)
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from fastapi import APIRouter, Request, Depends, Response, Security, encoders
|
from fastapi import APIRouter, Request, Depends, Response, encoders
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from app.db.session import get_db
|
from app.db.session import get_db
|
||||||
@@ -8,11 +8,9 @@ from app.db.crud import (
|
|||||||
create_user,
|
create_user,
|
||||||
delete_user,
|
delete_user,
|
||||||
edit_user,
|
edit_user,
|
||||||
assign_userrole,
|
|
||||||
get_roles,
|
|
||||||
)
|
)
|
||||||
from app.db.schemas import UserCreate, UserEdit, User, UserOut,Role
|
from app.db.schemas import UserCreate, UserEdit, User, UserOut
|
||||||
from app.core.auth import get_current_user,get_current_active_user, get_current_active_superuser
|
from app.core.auth import get_current_active_user, get_current_active_superuser
|
||||||
|
|
||||||
users_router = r = APIRouter()
|
users_router = r = APIRouter()
|
||||||
|
|
||||||
@@ -25,14 +23,14 @@ users_router = r = APIRouter()
|
|||||||
async def users_list(
|
async def users_list(
|
||||||
response: Response,
|
response: Response,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
current_user=Depends(get_current_active_user),
|
current_user=Depends(get_current_active_superuser),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Get all users
|
Get all users
|
||||||
"""
|
"""
|
||||||
users = get_users(db,current_user.is_superuser)
|
users = get_users(db)
|
||||||
# This is necessary for react-admin to work
|
# This is necessary for react-admin to work
|
||||||
#response.headers["Content-Range"] = f"0-9/{len(users)}"
|
response.headers["Content-Range"] = f"0-9/{len(users)}"
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
|
||||||
@@ -107,30 +105,3 @@ async def user_delete(
|
|||||||
Delete existing user
|
Delete existing user
|
||||||
"""
|
"""
|
||||||
return delete_user(db, user_id)
|
return delete_user(db, user_id)
|
||||||
|
|
||||||
|
|
||||||
@r.post("/userrole",
|
|
||||||
response_model=User,
|
|
||||||
response_model_exclude_none=True,)
|
|
||||||
async def assign_role(
|
|
||||||
request: Request,
|
|
||||||
userid:int,
|
|
||||||
roles:t.List[int],
|
|
||||||
db=Depends(get_db)
|
|
||||||
):
|
|
||||||
|
|
||||||
return assign_userrole(db,userid,roles)
|
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
|
||||||
"/roles",
|
|
||||||
response_model=t.List[Role],
|
|
||||||
response_model_exclude_none=True,
|
|
||||||
)
|
|
||||||
async def roles_list(
|
|
||||||
response: Response,
|
|
||||||
db=Depends(get_db),
|
|
||||||
current_user=Security(get_current_active_user, scopes=["role_list"]),
|
|
||||||
):
|
|
||||||
roles = get_roles(db)
|
|
||||||
return roles
|
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
from fastapi import HTTPException, status
|
|
||||||
import httpx
|
|
||||||
from app.db.schemas import ErrorCreate
|
|
||||||
from app.db.session import SessionLocal
|
|
||||||
from app.db.crud import create_log
|
|
||||||
|
|
||||||
class APIException(Exception):
|
|
||||||
def __init__(self, location: str, title: str, content: str, e: Exception):
|
|
||||||
self.detail = str(e)
|
|
||||||
self.status_code = 500
|
|
||||||
if isinstance(e,httpx.HTTPStatusError):
|
|
||||||
try:
|
|
||||||
error_response = e.response.json()
|
|
||||||
self.detail = error_response.get('message', self.detail)
|
|
||||||
self.status_code = e.response.status_code
|
|
||||||
content += self.detail
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
elif hasattr(e, 'detail'):
|
|
||||||
self.detail = e.detail
|
|
||||||
self.status_code = e.status_code if hasattr(e, 'status_code') else 500
|
|
||||||
content += e.detail
|
|
||||||
else:
|
|
||||||
self.detail = str(e)
|
|
||||||
self.status_code = 500
|
|
||||||
content += str(e)
|
|
||||||
|
|
||||||
if len(content) > 5000:
|
|
||||||
content = content[:5000]
|
|
||||||
|
|
||||||
self.error = ErrorCreate(location=location, title=title, content=content)
|
|
||||||
super().__init__(self.error)
|
|
||||||
|
|
||||||
def writedblog(exc: APIException):
|
|
||||||
db = SessionLocal()
|
|
||||||
try:
|
|
||||||
create_log(db,exc.error)
|
|
||||||
finally:
|
|
||||||
db.close()
|
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
from fastapi.security import SecurityScopes
|
|
||||||
import jwt
|
import jwt
|
||||||
from fastapi import Depends, HTTPException, Request, Security, status
|
from fastapi import Depends, HTTPException, status
|
||||||
from jwt import PyJWTError
|
from jwt import PyJWTError
|
||||||
|
|
||||||
from app.db import models, schemas, session
|
from app.db import models, schemas, session
|
||||||
from app.db.crud import get_user_by_email, create_user,get_user
|
from app.db.crud import get_user_by_email, create_user
|
||||||
from app.core import security
|
from app.core import security
|
||||||
|
|
||||||
|
|
||||||
async def get_current_user(security_scopes: SecurityScopes,
|
async def get_current_user(
|
||||||
db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme)
|
db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme)
|
||||||
):
|
):
|
||||||
credentials_exception = HTTPException(
|
credentials_exception = HTTPException(
|
||||||
@@ -17,25 +16,17 @@ async def get_current_user(security_scopes: SecurityScopes,
|
|||||||
headers={"WWW-Authenticate": "Bearer"},
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
|
|
||||||
payload = jwt.decode(
|
payload = jwt.decode(
|
||||||
token, security.SECRET_KEY, algorithms=[security.ALGORITHM]
|
token, security.SECRET_KEY, algorithms=[security.ALGORITHM]
|
||||||
)
|
)
|
||||||
id: int = payload.get("sub")
|
email: str = payload.get("sub")
|
||||||
if id is None:
|
if email is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
|
|
||||||
permissions: str = payload.get("permissions")
|
permissions: str = payload.get("permissions")
|
||||||
if not permissions =="ALL":
|
token_data = schemas.TokenData(email=email, permissions=permissions)
|
||||||
for scope in security_scopes.scopes:
|
|
||||||
if scope not in permissions.split(";"):
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=403, detail="The user doesn't have enough privileges"
|
|
||||||
)
|
|
||||||
token_data = schemas.TokenData(id = id, permissions=permissions)
|
|
||||||
except PyJWTError:
|
except PyJWTError:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
user = get_user(db, token_data.id)
|
user = get_user_by_email(db, token_data.email)
|
||||||
if user is None:
|
if user is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
return user
|
return user
|
||||||
@@ -67,7 +58,7 @@ def authenticate_user(db, email: str, password: str):
|
|||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
def sign_up_new_user(db, email: str, password: str, firstname: str,lastname: str):
|
def sign_up_new_user(db, email: str, password: str):
|
||||||
user = get_user_by_email(db, email)
|
user = get_user_by_email(db, email)
|
||||||
if user:
|
if user:
|
||||||
return False # User already exists
|
return False # User already exists
|
||||||
@@ -76,8 +67,6 @@ def sign_up_new_user(db, email: str, password: str, firstname: str,lastname: str
|
|||||||
schemas.UserCreate(
|
schemas.UserCreate(
|
||||||
email=email,
|
email=email,
|
||||||
password=password,
|
password=password,
|
||||||
first_name = firstname,
|
|
||||||
last_name = lastname,
|
|
||||||
is_active=True,
|
is_active=True,
|
||||||
is_superuser=False,
|
is_superuser=False,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,42 +1,18 @@
|
|||||||
import os
|
import os
|
||||||
import base64
|
|
||||||
|
|
||||||
|
|
||||||
PROJECT_NAME = "KintoneAppBuilder"
|
PROJECT_NAME = "KintoneAppBuilder"
|
||||||
|
|
||||||
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/dev"
|
SQLALCHEMY_DATABASE_URI = "mssql+pymssql://maxz64@maxzdb:m@xz1205@maxzdb.database.windows.net/alloc"
|
||||||
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://kabAdmin:P%40ssw0rd!@kintonetooldb.postgres.database.azure.com/dev_v2"
|
|
||||||
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/test"
|
BASE_URL = "https://mfu07rkgnb7c.cybozu.com"
|
||||||
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@ktune-prod-db.postgres.database.azure.com/postgres"
|
|
||||||
API_V1_STR = "/k/v1"
|
API_V1_STR = "/k/v1"
|
||||||
|
|
||||||
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
||||||
|
|
||||||
DEPLOY_MODE = "PROD" #DEV,PROD
|
API_V1_AUTH_VALUE = "TVhaOm1heHoxMjA1"
|
||||||
|
|
||||||
DEPLOY_JS_URL = "https://ka-addin.azurewebsites.net/alc_runtime.js"
|
KINTONE_USER = "MXZ"
|
||||||
|
|
||||||
KINTONE_FIELD_TYPE=["GROUP","GROUP_SELECT","CHECK_BOX","SUBTABLE","DROP_DOWN","USER_SELECT","RADIO_BUTTON","RICH_TEXT","LINK","REFERENCE_TABLE","CALC","TIME","NUMBER","ORGANIZATION_SELECT","FILE","DATETIME","DATE","MULTI_SELECT","SINGLE_LINE_TEXT","MULTI_LINE_TEXT"]
|
KINTONE_FIELD_TYPE=["GROUP","GROUP_SELECT","CHECK_BOX","SUBTABLE","DROP_DOWN","USER_SELECT","RADIO_BUTTON","RICH_TEXT","LINK","REFERENCE_TABLE","CALC","TIME","NUMBER","ORGANIZATION_SELECT","FILE","DATETIME","DATE","MULTI_SELECT","SINGLE_LINE_TEXT","MULTI_LINE_TEXT"]
|
||||||
|
|
||||||
KINTONE_FIELD_PROPERTY=['label','code','type','required','unique','maxValue','minValue','maxLength','minLength','defaultValue','defaultNowValue','options','expression','hideExpression','digit','protocol','displayScale','unit','unitPosition']
|
|
||||||
|
|
||||||
KINTONE_PSW_CRYPTO_KEY=bytes.fromhex("53 6c 93 bd 48 ad b5 c0 93 df a1 27 25 a1 a3 32 a2 03 3b a0 27 1f 51 dc 20 0e 6c d7 be fc fb ea")
|
|
||||||
|
|
||||||
class KINTONE_ENV:
|
|
||||||
|
|
||||||
BASE_URL = ""
|
|
||||||
|
|
||||||
API_V1_AUTH_VALUE = ""
|
|
||||||
|
|
||||||
KINTONE_USER = ""
|
|
||||||
|
|
||||||
DOMAIN_ID = ""
|
|
||||||
|
|
||||||
DOMAIN_NAME =""
|
|
||||||
|
|
||||||
def __init__(self,domain) -> None:
|
|
||||||
self.DOMAIN_NAME=domain.name
|
|
||||||
self.DOMAIN_ID=domain.id
|
|
||||||
self.BASE_URL = domain.url
|
|
||||||
self.KINTONE_USER = domain.kintoneuser
|
|
||||||
self.API_V1_AUTH_VALUE = base64.b64encode(bytes(f"{domain.kintoneuser}:{domain.decrypt_kintonepwd()}","utf-8"))
|
|
||||||
@@ -2,10 +2,6 @@ import jwt
|
|||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
|
|
||||||
import os
|
|
||||||
import base64
|
|
||||||
from app.core import config
|
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token")
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token")
|
||||||
|
|
||||||
@@ -13,7 +9,7 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|||||||
|
|
||||||
SECRET_KEY = "alicorns"
|
SECRET_KEY = "alicorns"
|
||||||
ALGORITHM = "HS256"
|
ALGORITHM = "HS256"
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES = 2880
|
ACCESS_TOKEN_EXPIRE_MINUTES = 30
|
||||||
|
|
||||||
|
|
||||||
def get_password_hash(password: str) -> str:
|
def get_password_hash(password: str) -> str:
|
||||||
@@ -29,36 +25,7 @@ def create_access_token(*, data: dict, expires_delta: timedelta = None):
|
|||||||
if expires_delta:
|
if expires_delta:
|
||||||
expire = datetime.utcnow() + expires_delta
|
expire = datetime.utcnow() + expires_delta
|
||||||
else:
|
else:
|
||||||
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
expire = datetime.utcnow() + timedelta(minutes=15)
|
||||||
to_encode.update({"exp": expire})
|
to_encode.update({"exp": expire})
|
||||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||||
return encoded_jwt
|
return encoded_jwt
|
||||||
|
|
||||||
def chacha20Encrypt(plaintext:str, key=config.KINTONE_PSW_CRYPTO_KEY):
|
|
||||||
if plaintext is None or plaintext == '':
|
|
||||||
return None
|
|
||||||
nonce = os.urandom(16)
|
|
||||||
algorithm = algorithms.ChaCha20(key, nonce)
|
|
||||||
cipher = Cipher(algorithm, mode=None)
|
|
||||||
encryptor = cipher.encryptor()
|
|
||||||
ciphertext = encryptor.update(plaintext.encode('utf-8')) + encryptor.finalize()
|
|
||||||
return base64.b64encode(nonce +'𒀸'.encode('utf-8')+ ciphertext).decode('utf-8')
|
|
||||||
|
|
||||||
def chacha20Decrypt(encoded_str:str, key=config.KINTONE_PSW_CRYPTO_KEY):
|
|
||||||
try:
|
|
||||||
decoded_data = base64.b64decode(encoded_str)
|
|
||||||
if len(decoded_data) < 18:
|
|
||||||
return encoded_str
|
|
||||||
special_char = decoded_data[16:20]
|
|
||||||
if special_char != '𒀸'.encode('utf-8'):
|
|
||||||
return encoded_str
|
|
||||||
nonce = decoded_data[:16]
|
|
||||||
ciphertext = decoded_data[20:]
|
|
||||||
except Exception as e:
|
|
||||||
print(f"An error occurred: {e}")
|
|
||||||
return encoded_str
|
|
||||||
algorithm = algorithms.ChaCha20(key, nonce)
|
|
||||||
cipher = Cipher(algorithm, mode=None)
|
|
||||||
decryptor = cipher.decryptor()
|
|
||||||
plaintext_bytes = decryptor.update(ciphertext) + decryptor.finalize()
|
|
||||||
return plaintext_bytes.decode('utf-8')
|
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
from datetime import datetime
|
|
||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from sqlalchemy import and_
|
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from . import models, schemas
|
from . import models, schemas
|
||||||
from app.core.security import chacha20Decrypt, get_password_hash
|
from app.core.security import get_password_hash
|
||||||
|
|
||||||
|
|
||||||
def get_user(db: Session, user_id: int):
|
def get_user(db: Session, user_id: int):
|
||||||
@@ -20,12 +18,9 @@ def get_user_by_email(db: Session, email: str) -> schemas.UserBase:
|
|||||||
|
|
||||||
|
|
||||||
def get_users(
|
def get_users(
|
||||||
db: Session, super:bool
|
db: Session, skip: int = 0, limit: int = 100
|
||||||
) -> t.List[schemas.UserOut]:
|
) -> t.List[schemas.UserOut]:
|
||||||
if super:
|
return db.query(models.User).offset(skip).limit(limit).all()
|
||||||
return db.query(models.User).all()
|
|
||||||
else:
|
|
||||||
return db.query(models.User).filter(models.User.is_superuser == False)
|
|
||||||
|
|
||||||
|
|
||||||
def create_user(db: Session, user: schemas.UserCreate):
|
def create_user(db: Session, user: schemas.UserCreate):
|
||||||
@@ -74,80 +69,6 @@ def edit_user(
|
|||||||
return db_user
|
return db_user
|
||||||
|
|
||||||
|
|
||||||
def get_roles(
|
|
||||||
db: Session
|
|
||||||
) -> t.List[schemas.Role]:
|
|
||||||
return db.query(models.Role).all()
|
|
||||||
|
|
||||||
def assign_userrole( db: Session, user_id: int, roles: t.List[int]):
|
|
||||||
db_user = db.query(models.User).get(user_id)
|
|
||||||
if db_user:
|
|
||||||
for role in db_user.roles:
|
|
||||||
db_user.roles.remove(role)
|
|
||||||
for roleid in roles:
|
|
||||||
role = db.query(models.Role).get(roleid)
|
|
||||||
if role:
|
|
||||||
db_user.roles.append(role)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_user)
|
|
||||||
return db_user
|
|
||||||
|
|
||||||
def get_apps(
|
|
||||||
db: Session,
|
|
||||||
domainurl:str
|
|
||||||
) -> t.List[schemas.AppList]:
|
|
||||||
return db.query(models.App).filter(models.App.domainurl == domainurl).all()
|
|
||||||
|
|
||||||
def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int):
|
|
||||||
db_app = db.query(models.App).filter(and_(models.App.domainurl == appedit.domainurl,models.App.appid == appedit.appid)).first()
|
|
||||||
if not db_app:
|
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
|
|
||||||
|
|
||||||
db_app.version = db_app.version + 1
|
|
||||||
appversion = models.AppVersion(
|
|
||||||
domainurl = appedit.domainurl,
|
|
||||||
appid=appedit.appid,
|
|
||||||
appname=db_app.appname,
|
|
||||||
version = db_app.version,
|
|
||||||
versionname = appedit.versionname,
|
|
||||||
comment = appedit.comment,
|
|
||||||
updateuserid = userid,
|
|
||||||
createuserid = userid
|
|
||||||
)
|
|
||||||
db.add(appversion)
|
|
||||||
db.add(db_app)
|
|
||||||
|
|
||||||
flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == appedit.domainurl,models.App.appid == appedit.appid))
|
|
||||||
for flow in flows:
|
|
||||||
db_flowhistory = models.FlowHistory(
|
|
||||||
flowid = flow.flowid,
|
|
||||||
appid = flow.appid,
|
|
||||||
eventid = flow.eventid,
|
|
||||||
domainurl = flow.domainurl,
|
|
||||||
name = flow.name,
|
|
||||||
content = flow.content,
|
|
||||||
createuser = userid,
|
|
||||||
version = db_app.version,
|
|
||||||
updateuserid = userid,
|
|
||||||
createuserid = userid
|
|
||||||
)
|
|
||||||
db.add(db_flowhistory)
|
|
||||||
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_app)
|
|
||||||
return db_app
|
|
||||||
|
|
||||||
def delete_apps(db: Session, domainurl: str,appid: str ):
|
|
||||||
db_app = db.query(models.App).filter(and_(models.App.domainurl == domainurl,models.App.appid ==appid)).first()
|
|
||||||
if not db_app:
|
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="App not found")
|
|
||||||
db.delete(db_app)
|
|
||||||
db_flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid ==appid))
|
|
||||||
for flow in db_flows:
|
|
||||||
db.delete(flow)
|
|
||||||
db.commit()
|
|
||||||
return db_app
|
|
||||||
|
|
||||||
def get_appsetting(db: Session, id: int):
|
def get_appsetting(db: Session, id: int):
|
||||||
app = db.query(models.AppSetting).get(id)
|
app = db.query(models.AppSetting).get(id)
|
||||||
if not app:
|
if not app:
|
||||||
@@ -195,245 +116,3 @@ def get_kintones(db: Session, type: int):
|
|||||||
if not kintones:
|
if not kintones:
|
||||||
raise HTTPException(status_code=404, detail="Data not found")
|
raise HTTPException(status_code=404, detail="Data not found")
|
||||||
return kintones
|
return kintones
|
||||||
|
|
||||||
def get_actions(db: Session):
|
|
||||||
actions = db.query(models.Action).all()
|
|
||||||
if not actions:
|
|
||||||
raise HTTPException(status_code=404, detail="Data not found")
|
|
||||||
return actions
|
|
||||||
|
|
||||||
|
|
||||||
def create_flow(db: Session, domainurl: str, flow: schemas.FlowIn,userid:int):
|
|
||||||
db_flow = models.Flow(
|
|
||||||
flowid=flow.flowid,
|
|
||||||
appid=flow.appid,
|
|
||||||
eventid=flow.eventid,
|
|
||||||
domainurl=domainurl,
|
|
||||||
name=flow.name,
|
|
||||||
content=flow.content,
|
|
||||||
createuserid = userid,
|
|
||||||
updateuserid = userid
|
|
||||||
)
|
|
||||||
db.add(db_flow)
|
|
||||||
db_app = db.query(models.App).filter(and_(models.App.domainurl == domainurl,models.App.appid == flow.appid)).first()
|
|
||||||
if not db_app:
|
|
||||||
db_app = models.App(
|
|
||||||
domainurl = domainurl,
|
|
||||||
appid=flow.appid,
|
|
||||||
appname=flow.appname,
|
|
||||||
version = 0,
|
|
||||||
createuserid= userid,
|
|
||||||
updateuserid = userid
|
|
||||||
)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_flow)
|
|
||||||
return db_flow
|
|
||||||
|
|
||||||
def delete_flow(db: Session, flowid: str):
|
|
||||||
flow = get_flow(db, flowid)
|
|
||||||
if not flow:
|
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
|
|
||||||
db.delete(flow)
|
|
||||||
db.commit()
|
|
||||||
return flow
|
|
||||||
|
|
||||||
|
|
||||||
def edit_flow(
|
|
||||||
db: Session, domainurl: str, flow: schemas.FlowIn,userid:int
|
|
||||||
) -> schemas.Flow:
|
|
||||||
db_flow = get_flow(db, flow.flowid)
|
|
||||||
if not db_flow:
|
|
||||||
#見つからない時新規作成
|
|
||||||
return create_flow(db,domainurl,flow,userid)
|
|
||||||
|
|
||||||
db_flow.appid =flow.appid
|
|
||||||
db_flow.eventid=flow.eventid
|
|
||||||
db_flow.domainurl=domainurl
|
|
||||||
db_flow.name=flow.name
|
|
||||||
db_flow.content=flow.content
|
|
||||||
db_flow.updateuserid = userid
|
|
||||||
|
|
||||||
db.add(db_flow)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_flow)
|
|
||||||
return db_flow
|
|
||||||
|
|
||||||
|
|
||||||
def get_flows(db: Session, flowid: str):
|
|
||||||
flows = db.query(models.Flow).all()
|
|
||||||
if not flows:
|
|
||||||
raise HTTPException(status_code=404, detail="Data not found")
|
|
||||||
return flows
|
|
||||||
|
|
||||||
def get_flow(db: Session, flowid: str):
|
|
||||||
flow = db.query(models.Flow).filter(models.Flow.flowid == flowid).first()
|
|
||||||
# if not flow:
|
|
||||||
# raise HTTPException(status_code=404, detail="Data not found")
|
|
||||||
return flow
|
|
||||||
|
|
||||||
def get_flows_by_app(db: Session,domainurl: str, appid: str):
|
|
||||||
flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid == appid)).all()
|
|
||||||
if not flows:
|
|
||||||
raise Exception("Data not found")
|
|
||||||
return flows
|
|
||||||
|
|
||||||
def create_domain(db: Session, domain: schemas.DomainBase,userid:int):
|
|
||||||
domain.encrypt_kintonepwd()
|
|
||||||
db_domain = models.Domain(
|
|
||||||
tenantid = domain.tenantid,
|
|
||||||
name=domain.name,
|
|
||||||
url=domain.url,
|
|
||||||
kintoneuser=domain.kintoneuser,
|
|
||||||
kintonepwd=domain.kintonepwd,
|
|
||||||
createuserid = userid,
|
|
||||||
updateuserid = userid
|
|
||||||
)
|
|
||||||
db.add(db_domain)
|
|
||||||
db.flush()
|
|
||||||
add_userdomain(db,userid,db_domain.id)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_domain)
|
|
||||||
return db_domain
|
|
||||||
|
|
||||||
def delete_domain(db: Session,id: int):
|
|
||||||
db_domain = db.query(models.Domain).get(id)
|
|
||||||
if not db_domain:
|
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
|
||||||
db.delete(db_domain)
|
|
||||||
db.commit()
|
|
||||||
return db_domain
|
|
||||||
|
|
||||||
|
|
||||||
def edit_domain(
|
|
||||||
db: Session, domain: schemas.DomainBase,userid:int
|
|
||||||
) -> schemas.Domain:
|
|
||||||
domain.encrypt_kintonepwd()
|
|
||||||
db_domain = db.query(models.Domain).get(domain.id)
|
|
||||||
if not db_domain:
|
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
|
||||||
db_domain.tenantid = domain.tenantid
|
|
||||||
db_domain.name=domain.name
|
|
||||||
db_domain.url=domain.url
|
|
||||||
db_domain.kintoneuser=domain.kintoneuser
|
|
||||||
db_domain.kintonepwd = domain.kintonepwd
|
|
||||||
db_domain.updateuserid = userid
|
|
||||||
db_domain.update_time = datetime.now
|
|
||||||
db.add(db_domain)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_domain)
|
|
||||||
return db_domain
|
|
||||||
|
|
||||||
|
|
||||||
def add_userdomain(db: Session, userid:int,domainid:int):
|
|
||||||
user_domain = models.UserDomain(userid = userid, domainid = domainid )
|
|
||||||
db.add(user_domain)
|
|
||||||
return user_domain
|
|
||||||
|
|
||||||
def add_userdomains(db: Session, userid:int,domainids:list[str]):
|
|
||||||
dbCommits = list(map(lambda domainid: models.UserDomain(userid = userid, domainid = domainid ), domainids))
|
|
||||||
db.bulk_save_objects(dbCommits)
|
|
||||||
db.commit()
|
|
||||||
return dbCommits
|
|
||||||
|
|
||||||
def delete_userdomain(db: Session, userid: int,domainid: int):
|
|
||||||
db_domain = db.query(models.UserDomain).filter(and_(models.UserDomain.userid == userid,models.UserDomain.domainid == domainid)).first()
|
|
||||||
if not db_domain:
|
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
|
||||||
db.delete(db_domain)
|
|
||||||
db.commit()
|
|
||||||
return db_domain
|
|
||||||
|
|
||||||
def active_userdomain(db: Session, userid: int,domainid: int):
|
|
||||||
db_userdomains = db.query(models.UserDomain).filter(models.UserDomain.userid == userid).all()
|
|
||||||
if not db_userdomains:
|
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
|
||||||
for domain in db_userdomains:
|
|
||||||
if domain.domainid == domainid:
|
|
||||||
domain.active = True
|
|
||||||
else:
|
|
||||||
domain.active = False
|
|
||||||
db.add(domain)
|
|
||||||
db.commit()
|
|
||||||
return db_userdomains
|
|
||||||
|
|
||||||
def get_activedomain(db: Session, userid: int)-> t.Optional[models.Domain]:
|
|
||||||
user_domains = (db.query(models.Domain,models.UserDomain.active)
|
|
||||||
.join(models.UserDomain,models.UserDomain.domainid == models.Domain.id )
|
|
||||||
.filter(models.UserDomain.userid == userid)
|
|
||||||
.all())
|
|
||||||
db_domain=None
|
|
||||||
if len(user_domains)==1:
|
|
||||||
db_domain = user_domains[0][0];
|
|
||||||
else:
|
|
||||||
db_domain = next((domain for domain,active in user_domains if active),None)
|
|
||||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
|
||||||
return db_domain
|
|
||||||
|
|
||||||
def get_domain(db: Session, userid: str):
|
|
||||||
domains = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(models.UserDomain.userid == userid).all()
|
|
||||||
# if not domains:
|
|
||||||
# raise HTTPException(status_code=404, detail="Data not found")
|
|
||||||
# for domain in domains:
|
|
||||||
# decrypted_pwd = chacha20Decrypt(domain.kintonepwd)
|
|
||||||
# domain.kintonepwd = decrypted_pwd
|
|
||||||
return domains
|
|
||||||
|
|
||||||
def get_domains(db: Session,tenantid:str):
|
|
||||||
domains = db.query(models.Domain).filter(models.Domain.tenantid == tenantid ).all()
|
|
||||||
if not domains:
|
|
||||||
raise HTTPException(status_code=404, detail="Data not found")
|
|
||||||
# for domain in domains:
|
|
||||||
# decrypted_pwd = chacha20Decrypt(domain.kintonepwd)
|
|
||||||
# domain.kintonepwd = decrypted_pwd
|
|
||||||
return domains
|
|
||||||
|
|
||||||
def get_events(db: Session):
|
|
||||||
events = db.query(models.Event).all()
|
|
||||||
if not events:
|
|
||||||
raise HTTPException(status_code=404, detail="Data not found")
|
|
||||||
return events
|
|
||||||
|
|
||||||
def get_category(db:Session):
|
|
||||||
categorys=db.query(models.Category).all()
|
|
||||||
return categorys
|
|
||||||
|
|
||||||
def get_eventactions(db: Session,eventid: str):
|
|
||||||
#eveactions = db.query(models.Action).join(models.EventAction,models.EventAction.actionid == models.Action.id ).join(models.Event,models.Event.id == models.EventAction.eventid).filter(models.Event.eventid == eventid).all()
|
|
||||||
#category = get_category(db)
|
|
||||||
blackactions = (
|
|
||||||
db.query(models.EventAction.actionid)
|
|
||||||
.filter(models.EventAction.eventid == eventid)
|
|
||||||
.subquery()
|
|
||||||
)
|
|
||||||
eveactions = (
|
|
||||||
db.query(
|
|
||||||
models.Action.id,
|
|
||||||
models.Action.name,
|
|
||||||
models.Action.title,
|
|
||||||
models.Action.subtitle,
|
|
||||||
models.Action.outputpoints,
|
|
||||||
models.Action.property,
|
|
||||||
models.Action.categoryid,
|
|
||||||
models.Action.nosort,
|
|
||||||
models.Category.categoryname)
|
|
||||||
.join(models.Category,models.Category.id == models.Action.categoryid)
|
|
||||||
.filter(models.Action.id.notin_(blackactions))
|
|
||||||
.order_by(models.Category.nosort,models.Action.nosort)
|
|
||||||
.all()
|
|
||||||
)
|
|
||||||
|
|
||||||
if not eveactions:
|
|
||||||
raise HTTPException(status_code=404, detail="Data not found")
|
|
||||||
return eveactions
|
|
||||||
|
|
||||||
|
|
||||||
def create_log(db: Session, error:schemas.ErrorCreate):
|
|
||||||
db_log = models.ErrorLog(title=error.title,location=error.location,content=error.content)
|
|
||||||
db.add(db_log)
|
|
||||||
db.commit()
|
|
||||||
db.refresh(db_log)
|
|
||||||
return db_log
|
|
||||||
|
|
||||||
def get_kintoneformat(db: Session):
|
|
||||||
formats = db.query(models.KintoneFormat).order_by(models.KintoneFormat.id).all()
|
|
||||||
return formats
|
|
||||||
@@ -1,221 +1,31 @@
|
|||||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey,Table
|
from sqlalchemy import Boolean, Column, Integer, String
|
||||||
from sqlalchemy.ext.declarative import as_declarative
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
from datetime import datetime
|
|
||||||
from app.db.session import Base
|
|
||||||
from app.core.security import chacha20Decrypt
|
|
||||||
|
|
||||||
@as_declarative()
|
from .session import Base
|
||||||
class Base:
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
|
||||||
create_time = Column(DateTime, default=datetime.now)
|
|
||||||
update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
|
||||||
|
|
||||||
|
|
||||||
userrole = Table(
|
|
||||||
"userrole",
|
|
||||||
Base.metadata,
|
|
||||||
Column("userid",Integer,ForeignKey("user.id")),
|
|
||||||
Column("roleid",Integer,ForeignKey("role.id")),
|
|
||||||
)
|
|
||||||
|
|
||||||
rolepermission = Table(
|
|
||||||
"rolepermission",
|
|
||||||
Base.metadata,
|
|
||||||
Column("roleid",Integer,ForeignKey("role.id")),
|
|
||||||
Column("permissionid",Integer,ForeignKey("permission.id")),
|
|
||||||
)
|
|
||||||
|
|
||||||
class User(Base):
|
class User(Base):
|
||||||
__tablename__ = "user"
|
__tablename__ = "user"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
email = Column(String(50), unique=True, index=True, nullable=False)
|
email = Column(String(50), unique=True, index=True, nullable=False)
|
||||||
first_name = Column(String(100))
|
first_name = Column(String(100))
|
||||||
last_name = Column(String(100))
|
last_name = Column(String(100))
|
||||||
hashed_password = Column(String(200), nullable=False)
|
hashed_password = Column(String(200), nullable=False)
|
||||||
is_active = Column(Boolean, default=True)
|
is_active = Column(Boolean, default=True)
|
||||||
is_superuser = Column(Boolean, default=False)
|
is_superuser = Column(Boolean, default=False)
|
||||||
roles = relationship("Role",secondary=userrole,back_populates="users")
|
|
||||||
|
|
||||||
|
|
||||||
class Role(Base):
|
|
||||||
__tablename__ = "role"
|
|
||||||
|
|
||||||
name = Column(String(100))
|
|
||||||
description = Column(String(255))
|
|
||||||
users = relationship("User",secondary=userrole,back_populates="roles")
|
|
||||||
permissions = relationship("Permission",secondary=rolepermission,back_populates="roles")
|
|
||||||
|
|
||||||
class Permission(Base):
|
|
||||||
__tablename__ = "permission"
|
|
||||||
|
|
||||||
menu = Column(String(100))
|
|
||||||
function = Column(String(255))
|
|
||||||
privilege = Column(String(100))
|
|
||||||
roles = relationship("Role",secondary=rolepermission,back_populates="permissions")
|
|
||||||
|
|
||||||
|
|
||||||
class App(Base):
|
|
||||||
__tablename__ = "app"
|
|
||||||
|
|
||||||
domainurl = Column(String(200), nullable=False)
|
|
||||||
appname = Column(String(200), nullable=False)
|
|
||||||
appid = Column(String(100), index=True, nullable=False)
|
|
||||||
version = Column(Integer)
|
|
||||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
createuser = relationship('User',foreign_keys=[createuserid])
|
|
||||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
|
||||||
|
|
||||||
class AppVersion(Base):
|
|
||||||
__tablename__ = "appversion"
|
|
||||||
|
|
||||||
domainurl = Column(String(200), nullable=False)
|
|
||||||
appname = Column(String(200), nullable=False)
|
|
||||||
appid = Column(String(100), index=True, nullable=False)
|
|
||||||
version = Column(Integer)
|
|
||||||
versionname = Column(String(200), nullable=False)
|
|
||||||
comment = Column(String(200), nullable=False)
|
|
||||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
createuser = relationship('User',foreign_keys=[createuserid])
|
|
||||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
|
||||||
|
|
||||||
|
|
||||||
class AppSetting(Base):
|
class AppSetting(Base):
|
||||||
__tablename__ = "appsetting"
|
__tablename__ = "appsetting"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
appid = Column(String(100), index=True, nullable=False)
|
appid = Column(String(100), index=True, nullable=False)
|
||||||
setting = Column(String(1000))
|
setting = Column(String(1000))
|
||||||
|
|
||||||
class Kintone(Base):
|
class Kintone(Base):
|
||||||
__tablename__ = "kintone"
|
__tablename__ = "kintone"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
type = Column(Integer, index=True, nullable=False)
|
type = Column(Integer, index=True, nullable=False)
|
||||||
name = Column(String(100), nullable=False)
|
name = Column(String(100), nullable=False)
|
||||||
desc = Column(String)
|
desc = Column(String)
|
||||||
content = Column(String)
|
content = Column(String)
|
||||||
|
|
||||||
class Action(Base):
|
|
||||||
__tablename__ = "action"
|
|
||||||
|
|
||||||
name = Column(String(100), index=True, nullable=False)
|
|
||||||
title = Column(String(200))
|
|
||||||
subtitle = Column(String(500))
|
|
||||||
outputpoints = Column(String)
|
|
||||||
property = Column(String)
|
|
||||||
categoryid = Column(Integer,ForeignKey("category.id"))
|
|
||||||
nosort = Column(Integer)
|
|
||||||
|
|
||||||
class Flow(Base):
|
|
||||||
__tablename__ = "flow"
|
|
||||||
|
|
||||||
flowid = Column(String(100), index=True, nullable=False)
|
|
||||||
appid = Column(String(100), index=True, nullable=False)
|
|
||||||
eventid = Column(String(100), index=True, nullable=False)
|
|
||||||
domainurl = Column(String(200))
|
|
||||||
name = Column(String(200))
|
|
||||||
content = Column(String)
|
|
||||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
createuser = relationship('User',foreign_keys=[createuserid])
|
|
||||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
|
||||||
|
|
||||||
class FlowHistory(Base):
|
|
||||||
__tablename__ = "flowhistory"
|
|
||||||
|
|
||||||
flowid = Column(String(100), index=True, nullable=False)
|
|
||||||
appid = Column(String(100), index=True, nullable=False)
|
|
||||||
eventid = Column(String(100), index=True, nullable=False)
|
|
||||||
domainurl = Column(String(200))
|
|
||||||
name = Column(String(200))
|
|
||||||
content = Column(String)
|
|
||||||
version = Column(Integer)
|
|
||||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
createuser = relationship('User',foreign_keys=[createuserid])
|
|
||||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
|
||||||
|
|
||||||
class Tenant(Base):
|
|
||||||
__tablename__ = "tenant"
|
|
||||||
|
|
||||||
tenantid = Column(String(100), index=True, nullable=False)
|
|
||||||
name = Column(String(200))
|
|
||||||
licence = Column(String(200))
|
|
||||||
startdate = Column(DateTime)
|
|
||||||
enddate = Column(DateTime)
|
|
||||||
|
|
||||||
class Domain(Base):
|
|
||||||
__tablename__ = "domain"
|
|
||||||
|
|
||||||
tenantid = Column(String(100), index=True, nullable=False)
|
|
||||||
name = Column(String(100), nullable=False)
|
|
||||||
url = Column(String(200), nullable=False)
|
|
||||||
kintoneuser = Column(String(100), nullable=False)
|
|
||||||
kintonepwd = Column(String(100), nullable=False)
|
|
||||||
def decrypt_kintonepwd(self):
|
|
||||||
decrypted_pwd = chacha20Decrypt(self.kintonepwd)
|
|
||||||
return decrypted_pwd
|
|
||||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
createuser = relationship('User',foreign_keys=[createuserid])
|
|
||||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
|
||||||
|
|
||||||
class UserDomain(Base):
|
|
||||||
__tablename__ = "userdomain"
|
|
||||||
|
|
||||||
userid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
domainid = Column(Integer,ForeignKey("domain.id"))
|
|
||||||
active = Column(Boolean, default=False)
|
|
||||||
|
|
||||||
class Event(Base):
|
|
||||||
__tablename__ = "event"
|
|
||||||
|
|
||||||
category = Column(String(100), nullable=False)
|
|
||||||
type = Column(String(100), nullable=False)
|
|
||||||
eventid= Column(String(100), nullable=False)
|
|
||||||
function = Column(String(500), nullable=False)
|
|
||||||
mobile = Column(Boolean, default=False)
|
|
||||||
eventgroup = Column(Boolean, default=False)
|
|
||||||
|
|
||||||
class EventAction(Base):
|
|
||||||
__tablename__ = "eventaction"
|
|
||||||
|
|
||||||
eventid = Column(String(100),ForeignKey("event.eventid"))
|
|
||||||
actionid = Column(Integer,ForeignKey("action.id"))
|
|
||||||
|
|
||||||
|
|
||||||
class ErrorLog(Base):
|
|
||||||
__tablename__ = "errorlog"
|
|
||||||
|
|
||||||
title = Column(String(50))
|
|
||||||
location = Column(String(500))
|
|
||||||
content = Column(String(5000))
|
|
||||||
|
|
||||||
class OperationLog(Base):
|
|
||||||
__tablename__ = "operationlog"
|
|
||||||
|
|
||||||
tenantid = Column(String(100))
|
|
||||||
domainurl = Column(String(200))
|
|
||||||
userid = Column(Integer,ForeignKey("user.id"))
|
|
||||||
operation = Column(String(200))
|
|
||||||
function = Column(String(200))
|
|
||||||
detail = Column(String(200))
|
|
||||||
user = relationship('User')
|
|
||||||
|
|
||||||
class KintoneFormat(Base):
|
|
||||||
__tablename__ = "kintoneformat"
|
|
||||||
|
|
||||||
name = Column(String(50))
|
|
||||||
startrow =Column(Integer)
|
|
||||||
startcolumn =Column(Integer)
|
|
||||||
typecolumn =Column(Integer)
|
|
||||||
codecolumn =Column(Integer)
|
|
||||||
field = Column(String(5000))
|
|
||||||
trueformat = Column(String(10))
|
|
||||||
|
|
||||||
class Category(Base):
|
|
||||||
__tablename__ = "category"
|
|
||||||
|
|
||||||
categoryname = Column(String(20))
|
|
||||||
nosort = Column(Integer)
|
|
||||||
@@ -1,25 +1,6 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from datetime import datetime
|
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from app.core.security import chacha20Decrypt, chacha20Encrypt
|
|
||||||
|
|
||||||
class Base(BaseModel):
|
|
||||||
create_time: datetime
|
|
||||||
update_time: datetime
|
|
||||||
|
|
||||||
|
|
||||||
class Permission(BaseModel):
|
|
||||||
id: int
|
|
||||||
menu:str
|
|
||||||
function:str
|
|
||||||
privilege:str
|
|
||||||
|
|
||||||
class Role(BaseModel):
|
|
||||||
id: int
|
|
||||||
name:str
|
|
||||||
description:str
|
|
||||||
permissions:t.List[Permission] = []
|
|
||||||
|
|
||||||
class UserBase(BaseModel):
|
class UserBase(BaseModel):
|
||||||
email: str
|
email: str
|
||||||
@@ -27,35 +8,30 @@ class UserBase(BaseModel):
|
|||||||
is_superuser: bool = False
|
is_superuser: bool = False
|
||||||
first_name: str = None
|
first_name: str = None
|
||||||
last_name: str = None
|
last_name: str = None
|
||||||
roles:t.List[Role] = []
|
|
||||||
|
|
||||||
class UserOut(UserBase):
|
class UserOut(UserBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UserCreate(UserBase):
|
class UserCreate(UserBase):
|
||||||
email:str
|
|
||||||
password: str
|
password: str
|
||||||
first_name: str
|
|
||||||
last_name: str
|
|
||||||
is_active:bool
|
|
||||||
is_superuser:bool
|
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
class UserEdit(UserBase):
|
class UserEdit(UserBase):
|
||||||
password: t.Optional[str] = None
|
password: t.Optional[str] = None
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
class User(UserBase):
|
class User(UserBase):
|
||||||
id: int
|
id: int
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
@@ -63,23 +39,8 @@ class Token(BaseModel):
|
|||||||
access_token: str
|
access_token: str
|
||||||
token_type: str
|
token_type: str
|
||||||
|
|
||||||
class AppList(Base):
|
|
||||||
domainurl: str
|
|
||||||
appname: str
|
|
||||||
appid:str
|
|
||||||
updateuser: UserOut
|
|
||||||
version:int
|
|
||||||
|
|
||||||
class AppVersion(BaseModel):
|
|
||||||
domainurl: str
|
|
||||||
appname: str
|
|
||||||
versionname: str
|
|
||||||
comment:str
|
|
||||||
appid:str
|
|
||||||
|
|
||||||
|
|
||||||
class TokenData(BaseModel):
|
class TokenData(BaseModel):
|
||||||
id:int = 0
|
|
||||||
email: str = None
|
email: str = None
|
||||||
permissions: str = "user"
|
permissions: str = "user"
|
||||||
|
|
||||||
@@ -95,7 +56,7 @@ class AppBase(BaseModel):
|
|||||||
class App(AppBase):
|
class App(AppBase):
|
||||||
id: int
|
id: int
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
@@ -106,78 +67,5 @@ class Kintone(BaseModel):
|
|||||||
desc: str = None
|
desc: str = None
|
||||||
content: str = None
|
content: str = None
|
||||||
|
|
||||||
class ConfigDict:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
class Action(BaseModel):
|
|
||||||
id: int
|
|
||||||
name: str = None
|
|
||||||
title: str = None
|
|
||||||
subtitle: str = None
|
|
||||||
outputpoints: str = None
|
|
||||||
property: str = None
|
|
||||||
categoryid: int = None
|
|
||||||
nosort: int
|
|
||||||
categoryname : str =None
|
|
||||||
class ConfigDict:
|
|
||||||
orm_mode = True
|
|
||||||
|
|
||||||
class FlowIn(BaseModel):
|
|
||||||
flowid: str
|
|
||||||
# domainurl:str
|
|
||||||
appid: str
|
|
||||||
appname:str
|
|
||||||
eventid: str
|
|
||||||
name: str = None
|
|
||||||
content: str = None
|
|
||||||
|
|
||||||
class Flow(Base):
|
|
||||||
id: int
|
|
||||||
flowid: str
|
|
||||||
appid: str
|
|
||||||
eventid: str
|
|
||||||
domainurl: str
|
|
||||||
name: str = None
|
|
||||||
content: str = None
|
|
||||||
|
|
||||||
class ConfigDict:
|
|
||||||
orm_mode = True
|
|
||||||
|
|
||||||
class DomainBase(BaseModel):
|
|
||||||
id: int
|
|
||||||
tenantid: str
|
|
||||||
name: str
|
|
||||||
url: str
|
|
||||||
kintoneuser: str
|
|
||||||
kintonepwd: str
|
|
||||||
|
|
||||||
def encrypt_kintonepwd(self):
|
|
||||||
encrypted_pwd = chacha20Encrypt(self.kintonepwd)
|
|
||||||
self.kintonepwd = encrypted_pwd
|
|
||||||
|
|
||||||
class Domain(Base):
|
|
||||||
id: int
|
|
||||||
tenantid: str
|
|
||||||
name: str
|
|
||||||
url: str
|
|
||||||
kintoneuser: str
|
|
||||||
kintonepwd: str
|
|
||||||
class ConfigDict:
|
|
||||||
orm_mode = True
|
|
||||||
|
|
||||||
class Event(Base):
|
|
||||||
id: int
|
|
||||||
category: str
|
|
||||||
type: str
|
|
||||||
eventid: str
|
|
||||||
function: str
|
|
||||||
mobile: bool
|
|
||||||
eventgroup: bool
|
|
||||||
|
|
||||||
class ConfigDict:
|
|
||||||
orm_mode = True
|
|
||||||
|
|
||||||
class ErrorCreate(BaseModel):
|
|
||||||
title:str
|
|
||||||
location:str
|
|
||||||
content:str
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import os
|
|
||||||
from fastapi import FastAPI, Depends
|
from fastapi import FastAPI, Depends
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
import uvicorn
|
import uvicorn
|
||||||
@@ -12,11 +11,6 @@ from app.core.auth import get_current_active_user
|
|||||||
from app.core.celery_app import celery_app
|
from app.core.celery_app import celery_app
|
||||||
from app import tasks
|
from app import tasks
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
import logging
|
|
||||||
from app.core.apiexception import APIException, writedblog
|
|
||||||
from app.db.crud import create_log
|
|
||||||
from fastapi.responses import JSONResponse
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
@@ -25,7 +19,9 @@ app = FastAPI(
|
|||||||
)
|
)
|
||||||
|
|
||||||
origins = [
|
origins = [
|
||||||
"*"
|
"http://localhost:9000",
|
||||||
|
"http://localhost",
|
||||||
|
"http://localhost:8080",
|
||||||
]
|
]
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
@@ -43,25 +39,6 @@ app.add_middleware(
|
|||||||
# request.state.db.close()
|
# request.state.db.close()
|
||||||
# return response
|
# return response
|
||||||
|
|
||||||
@app.on_event("startup")
|
|
||||||
async def startup_event():
|
|
||||||
log_dir="log"
|
|
||||||
if not os.path.exists(log_dir):
|
|
||||||
os.makedirs(log_dir)
|
|
||||||
|
|
||||||
logger = logging.getLogger("uvicorn.access")
|
|
||||||
handler = logging.handlers.RotatingFileHandler(f"{log_dir}/api.log",mode="a",maxBytes = 100*1024, backupCount = 3)
|
|
||||||
handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
||||||
logger.addHandler(handler)
|
|
||||||
|
|
||||||
@app.exception_handler(APIException)
|
|
||||||
async def api_exception_handler(request: Request, exc: APIException):
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.run_in_executor(None,writedblog,exc)
|
|
||||||
return JSONResponse(
|
|
||||||
status_code=exc.status_code,
|
|
||||||
content={"detail": f"{exc.detail}"},
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.get("/api/v1")
|
@app.get("/api/v1")
|
||||||
async def root():
|
async def root():
|
||||||
|
|||||||
@@ -25,7 +25,3 @@ python -m venv env
|
|||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
```
|
```
|
||||||
4. backend 起動
|
|
||||||
```bash
|
|
||||||
uvicorn app.main:app --reload
|
|
||||||
```
|
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -0,0 +1,10 @@
|
|||||||
|
<mxfile host="65bd71144e">
|
||||||
|
<diagram id="UMFNb7zEleFpzTH4EAp9" name="ページ1">
|
||||||
|
<mxGraphModel dx="1202" dy="612" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
</mxfile>
|
||||||
Binary file not shown.
BIN
document/Kintone自動化ツール画面設計.xlsx
Normal file
BIN
document/Kintone自動化ツール画面設計.xlsx
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
1479
document/Kintone自動化ツール設計図2.drawio
Normal file
1479
document/Kintone自動化ツール設計図2.drawio
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 56 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,147 +0,0 @@
|
|||||||
<mxfile host="app.diagrams.net" modified="2024-02-21T05:42:02.026Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36" etag="T2S5cjvthSOlO5DmGw-C" version="23.1.5" type="device">
|
|
||||||
<diagram id="Z6uZM46JtkVaKDzPjE9h" name="サイトマップ">
|
|
||||||
<mxGraphModel dx="1434" dy="820" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
|
||||||
<root>
|
|
||||||
<mxCell id="0" />
|
|
||||||
<mxCell id="1" parent="0" />
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-1" target="Gi77RX5G2m4J9-6cMje4-13" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-1" value="テナント登録" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.login;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="60" y="50" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-2" value="Admin Login" style="html=1;whiteSpace=wrap;strokeColor=#2D7600;fillColor=#60a917;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.login;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="60" y="270" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-8" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-5" target="Gi77RX5G2m4J9-6cMje4-7" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-9" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-5" target="Gi77RX5G2m4J9-6cMje4-7" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-12" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-5" target="Gi77RX5G2m4J9-6cMje4-11" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-5" value="Home" style="html=1;whiteSpace=wrap;strokeColor=#2D7600;fillColor=#60a917;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="240" y="270" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-6" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-2" target="Gi77RX5G2m4J9-6cMje4-5" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-42" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-7" target="Gi77RX5G2m4J9-6cMje4-41" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-7" value="ユーザー登録" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="440" y="220" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-11" value="ドメイン登録" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="440" y="340" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-13" value="テナント管理者作成" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.login;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="240" y="50" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-15" value="ライセンス情報" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="480" y="10" width="90" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-16" value="Adminユーザー" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="480" y="90" width="90" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-13" target="Gi77RX5G2m4J9-6cMje4-15" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-13" target="Gi77RX5G2m4J9-6cMje4-16" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-19" value="テナントDB<br>作成" style="shape=cylinder3;whiteSpace=wrap;html=1;boundedLbl=1;backgroundOutline=1;size=15;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="550" y="50" width="90" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-20" target="Gi77RX5G2m4J9-6cMje4-21" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-20" value="Login" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;sketch=0;shape=mxgraph.sitemap.login;fontColor=#ffffff;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="50" y="610" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-24" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-21" target="Gi77RX5G2m4J9-6cMje4-25" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry">
|
|
||||||
<mxPoint x="430" y="645" as="targetPoint" />
|
|
||||||
</mxGeometry>
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-21" value="Home" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="230" y="610" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-27" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-25" target="Gi77RX5G2m4J9-6cMje4-26" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-25" value="アプリ一覧" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="440" y="610" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-29" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-26" target="Gi77RX5G2m4J9-6cMje4-28" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-26" value="フロー一覧" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="620" y="610" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-40" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-28" target="Gi77RX5G2m4J9-6cMje4-39" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-28" value="フローエディタ" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="800" y="610" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-33" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-30" target="Gi77RX5G2m4J9-6cMje4-32" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-30" value="設計書取込" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="440" y="715" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-31" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-21" target="Gi77RX5G2m4J9-6cMje4-30" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-32" value="取込結果表示" style="html=1;whiteSpace=wrap;strokeColor=#005700;fillColor=#008a00;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#ffffff;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="620" y="715" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-37" value="設計書ダウロード" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="440" y="825" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-38" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-21" target="Gi77RX5G2m4J9-6cMje4-37" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-39" value="フロー履歴管理" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.news;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="980" y="610" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-41" value="ALC設定" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="620" y="220" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-43" value="管理ドメイン設定" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="800" y="220" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-44" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-41" target="Gi77RX5G2m4J9-6cMje4-43" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-45" value="プロファイル" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="440" y="935" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-46" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-21" target="Gi77RX5G2m4J9-6cMje4-45" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-50" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="1" source="Gi77RX5G2m4J9-6cMje4-47" target="Gi77RX5G2m4J9-6cMje4-49" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-47" value="プロファイル" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="440" y="450" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-48" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-5" target="Gi77RX5G2m4J9-6cMje4-47" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-49" value="ライセンス情報" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="620" y="450" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-51" value="ライセンス情報" style="html=1;whiteSpace=wrap;strokeColor=none;fillColor=#0079D6;labelPosition=center;verticalLabelPosition=middle;verticalAlign=top;align=center;fontSize=12;outlineConnect=0;spacingTop=-6;fontColor=#FFFFFF;sketch=0;shape=mxgraph.sitemap.home;" parent="1" vertex="1">
|
|
||||||
<mxGeometry x="620" y="935" width="120" height="70" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
<mxCell id="Gi77RX5G2m4J9-6cMje4-52" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="1" source="Gi77RX5G2m4J9-6cMje4-45" target="Gi77RX5G2m4J9-6cMje4-51" edge="1">
|
|
||||||
<mxGeometry relative="1" as="geometry" />
|
|
||||||
</mxCell>
|
|
||||||
</root>
|
|
||||||
</mxGraphModel>
|
|
||||||
</diagram>
|
|
||||||
</mxfile>
|
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,2 @@
|
|||||||
#開発環境
|
KAB_BACKEND_URL="http://127.0.0.1:8000/api/v1/"
|
||||||
#KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
|
|
||||||
#単体テスト環境
|
|
||||||
#KAB_BACKEND_URL="https://kab-backend-unittest.azurewebsites.net/"
|
|
||||||
#ローカル開発環境
|
|
||||||
KAB_BACKEND_URL="http://127.0.0.1:8000/"
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
#KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
|
VUE_BACKEND_URL="http://localhost:8000/api/"
|
||||||
KAB_BACKEND_URL="http://127.0.0.1:8000/"
|
|
||||||
|
|||||||
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@@ -35,6 +35,3 @@ yarn-error.log*
|
|||||||
|
|
||||||
# local .env files
|
# local .env files
|
||||||
.env.local*
|
.env.local*
|
||||||
|
|
||||||
# pnpm
|
|
||||||
pnpm-lock.yaml
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="ja-jp">
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title><%= productName %></title>
|
<title><%= productName %></title>
|
||||||
|
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="description" content="<%= productDescription %>">
|
<meta name="description" content="<%= productDescription %>">
|
||||||
<meta name="format-detection" content="telephone=no">
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
|||||||
72
frontend/package-lock.json
generated
72
frontend/package-lock.json
generated
@@ -10,16 +10,13 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.16.4",
|
"@quasar/extras": "^1.16.4",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"pinia": "^2.1.6",
|
|
||||||
"quasar": "^2.6.0",
|
"quasar": "^2.6.0",
|
||||||
"uuid": "^9.0.0",
|
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-router": "^4.0.0"
|
"vue-router": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@quasar/app-vite": "^1.3.0",
|
"@quasar/app-vite": "^1.3.0",
|
||||||
"@types/node": "^12.20.21",
|
"@types/node": "^12.20.21",
|
||||||
"@types/uuid": "^9.0.3",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||||
"@typescript-eslint/parser": "^5.10.0",
|
"@typescript-eslint/parser": "^5.10.0",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
@@ -31,9 +28,8 @@
|
|||||||
"typescript": "^4.5.4"
|
"typescript": "^4.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20 ||^18 || ^16 || ^14.19",
|
"node": "^18 || ^16 || ^14.19",
|
||||||
"npm": ">= 6.13.4",
|
"npm": ">= 6.13.4",
|
||||||
"pnpm": ">=8.6.0",
|
|
||||||
"yarn": ">= 1.21.1"
|
"yarn": ">= 1.21.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -548,12 +544,6 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/uuid": {
|
|
||||||
"version": "9.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.3.tgz",
|
|
||||||
"integrity": "sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "5.61.0",
|
"version": "5.61.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz",
|
||||||
@@ -4080,56 +4070,6 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/pinia": {
|
|
||||||
"version": "2.1.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz",
|
|
||||||
"integrity": "sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"@vue/devtools-api": "^6.5.0",
|
|
||||||
"vue-demi": ">=0.14.5"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/posva"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue/composition-api": "^1.4.0",
|
|
||||||
"typescript": ">=4.4.4",
|
|
||||||
"vue": "^2.6.14 || ^3.3.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@vue/composition-api": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"typescript": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/pinia/node_modules/vue-demi": {
|
|
||||||
"version": "0.14.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
|
|
||||||
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
|
||||||
"hasInstallScript": true,
|
|
||||||
"bin": {
|
|
||||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
|
||||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/antfu"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@vue/composition-api": "^1.0.0-rc.1",
|
|
||||||
"vue": "^3.0.0-0 || ^2.6.0"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@vue/composition-api": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.25",
|
"version": "8.4.25",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz",
|
||||||
@@ -5006,7 +4946,7 @@
|
|||||||
"version": "4.9.5",
|
"version": "4.9.5",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
|
||||||
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
|
||||||
"devOptional": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -5105,14 +5045,6 @@
|
|||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/uuid": {
|
|
||||||
"version": "9.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
|
||||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
|
||||||
"bin": {
|
|
||||||
"uuid": "dist/bin/uuid"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/vary": {
|
"node_modules/vary": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "k-tune",
|
"name": "kintone-app-builder",
|
||||||
"version": "0.2.0",
|
"version": "0.0.1",
|
||||||
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
|
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
|
||||||
"productName": "k-tune | kintoneジェネレーター",
|
"productName": "Kintone App Builder",
|
||||||
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",
|
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -10,26 +10,18 @@
|
|||||||
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
"format": "prettier --write \"**/*.{js,ts,vue,scss,html,md,json}\" --ignore-path .gitignore",
|
||||||
"test": "echo \"No test specified\" && exit 0",
|
"test": "echo \"No test specified\" && exit 0",
|
||||||
"dev": "quasar dev",
|
"dev": "quasar dev",
|
||||||
"dev:local": "set \"LOCAL=true\" && quasar dev",
|
"build": "quasar build"
|
||||||
"build": "set \"SOURCE_MAP=false\" && quasar build",
|
|
||||||
"build:dev": "set \"SOURCE_MAP=true\" && quasar build"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.16.4",
|
"@quasar/extras": "^1.16.4",
|
||||||
"@vueuse/core": "^10.9.0",
|
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"jwt-decode": "^4.0.0",
|
|
||||||
"pinia": "^2.1.7",
|
|
||||||
"pinia-plugin-persistedstate": "^3.2.1",
|
|
||||||
"quasar": "^2.6.0",
|
"quasar": "^2.6.0",
|
||||||
"uuid": "^9.0.0",
|
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
"vue-router": "^4.0.0"
|
"vue-router": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@quasar/app-vite": "^1.3.0",
|
"@quasar/app-vite": "^1.3.0",
|
||||||
"@types/node": "^12.20.21",
|
"@types/node": "^12.20.21",
|
||||||
"@types/uuid": "^9.0.3",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
"@typescript-eslint/eslint-plugin": "^5.10.0",
|
||||||
"@typescript-eslint/parser": "^5.10.0",
|
"@typescript-eslint/parser": "^5.10.0",
|
||||||
"autoprefixer": "^10.4.2",
|
"autoprefixer": "^10.4.2",
|
||||||
@@ -41,9 +33,8 @@
|
|||||||
"typescript": "^4.5.4"
|
"typescript": "^4.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^20 ||^18 || ^16 || ^14.19",
|
"node": "^18 || ^16 || ^14.19",
|
||||||
"npm": ">= 6.13.4",
|
"npm": ">= 6.13.4",
|
||||||
"yarn": ">= 1.21.1",
|
"yarn": ">= 1.21.1"
|
||||||
"pnpm": ">=8.6.0"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
<configuration>
|
|
||||||
<system.webServer>
|
|
||||||
<staticContent>
|
|
||||||
<mimeMap fileExtension=".woff" mimeType="application/font-woff" />
|
|
||||||
<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />
|
|
||||||
</staticContent>
|
|
||||||
</system.webServer>
|
|
||||||
</configuration>
|
|
||||||
@@ -10,14 +10,10 @@
|
|||||||
|
|
||||||
|
|
||||||
const { configure } = require('quasar/wrappers');
|
const { configure } = require('quasar/wrappers');
|
||||||
const envPath = process.env.LOCAL==='true'?'.env.development':'.env';
|
const dotenv = require('dotenv').config().parsed;
|
||||||
const dotenv = require('dotenv').config({path:envPath}).parsed;
|
const package = require('./package.json');
|
||||||
console.log('dotenv=>',dotenv);
|
|
||||||
// const package = require('./package.json');
|
|
||||||
const { Notify } = require('quasar');
|
const { Notify } = require('quasar');
|
||||||
const version = process.env.npm_package_version;
|
const version = package.version;
|
||||||
const productName=process.env.npm_package_productName;
|
|
||||||
// console.log(process.env);
|
|
||||||
module.exports = configure(function (/* ctx */) {
|
module.exports = configure(function (/* ctx */) {
|
||||||
return {
|
return {
|
||||||
eslint: {
|
eslint: {
|
||||||
@@ -36,8 +32,7 @@ module.exports = configure(function (/* ctx */) {
|
|||||||
// --> boot files are part of "main.js"
|
// --> boot files are part of "main.js"
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
// https://v2.quasar.dev/quasar-cli-vite/boot-files
|
||||||
boot: [
|
boot: [
|
||||||
'axios',
|
'axios'
|
||||||
'error-handler'
|
|
||||||
],
|
],
|
||||||
|
|
||||||
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
// https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css
|
||||||
@@ -54,6 +49,7 @@ module.exports = configure(function (/* ctx */) {
|
|||||||
// 'themify',
|
// 'themify',
|
||||||
// 'line-awesome',
|
// 'line-awesome',
|
||||||
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
// 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
|
||||||
|
|
||||||
'roboto-font', // optional, you are not bound to it
|
'roboto-font', // optional, you are not bound to it
|
||||||
'material-icons', // optional, you are not bound to it
|
'material-icons', // optional, you are not bound to it
|
||||||
],
|
],
|
||||||
@@ -64,7 +60,6 @@ module.exports = configure(function (/* ctx */) {
|
|||||||
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
|
browser: ['es2019', 'edge88', 'firefox78', 'chrome87', 'safari13.1'],
|
||||||
node: 'node16'
|
node: 'node16'
|
||||||
},
|
},
|
||||||
sourcemap:process.env.SOURCE_MAP === 'true',
|
|
||||||
|
|
||||||
vueRouterMode: 'hash', // available values: 'hash', 'history'
|
vueRouterMode: 'hash', // available values: 'hash', 'history'
|
||||||
// vueRouterBase,
|
// vueRouterBase,
|
||||||
@@ -75,7 +70,7 @@ module.exports = configure(function (/* ctx */) {
|
|||||||
|
|
||||||
// publicPath: '/',
|
// publicPath: '/',
|
||||||
// analyze: true,
|
// analyze: true,
|
||||||
env: { ...dotenv, version ,productName},
|
env: { ...dotenv, version },
|
||||||
// rawDefine: {}
|
// rawDefine: {}
|
||||||
// ignorePublicFolder: true,
|
// ignorePublicFolder: true,
|
||||||
// minify: false,
|
// minify: false,
|
||||||
@@ -94,7 +89,6 @@ module.exports = configure(function (/* ctx */) {
|
|||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||||
devServer: {
|
devServer: {
|
||||||
// https: true
|
// https: true
|
||||||
port:9001,
|
|
||||||
open: true, // opens browser window automatically
|
open: true, // opens browser window automatically
|
||||||
env: { ...dotenv },
|
env: { ...dotenv },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { boot } from 'quasar/wrappers';
|
import { boot } from 'quasar/wrappers';
|
||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import {router} from 'src/router';
|
|
||||||
|
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module '@vue/runtime-core' {
|
||||||
interface ComponentCustomProperties {
|
interface ComponentCustomProperties {
|
||||||
@@ -16,10 +14,11 @@ declare module '@vue/runtime-core' {
|
|||||||
// good idea to move this instance creation inside of the
|
// good idea to move this instance creation inside of the
|
||||||
// "export default () => {}" function below (which runs individually
|
// "export default () => {}" function below (which runs individually
|
||||||
// for each client)
|
// for each client)
|
||||||
|
|
||||||
const api:AxiosInstance = axios.create({ baseURL: process.env.KAB_BACKEND_URL });
|
const api:AxiosInstance = axios.create({ baseURL: process.env.KAB_BACKEND_URL });
|
||||||
|
|
||||||
export default boot(({ app }) => {
|
export default boot(({ app }) => {
|
||||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||||
|
|
||||||
app.config.globalProperties.$axios = axios;
|
app.config.globalProperties.$axios = axios;
|
||||||
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
||||||
// so you won't necessarily have to import axios in each vue file
|
// so you won't necessarily have to import axios in each vue file
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
// src/boot/error-handler.ts
|
|
||||||
import { boot } from 'quasar/wrappers';
|
|
||||||
import { Router } from 'vue-router';
|
|
||||||
import { App } from 'vue';
|
|
||||||
|
|
||||||
export default boot(({ app, router }: { app: App<Element>; router: Router }) => {
|
|
||||||
document.documentElement.lang="ja-JP";
|
|
||||||
app.config.errorHandler = (err: any, instance: any, info: string) => {
|
|
||||||
if (err.response && err.response.status === 401) {
|
|
||||||
// 認証エラーの場合再ログインする
|
|
||||||
console.error('(; ゚Д゚)/認証エラー(401):', err, info);
|
|
||||||
localStorage.removeItem('token');
|
|
||||||
router.replace({
|
|
||||||
path:"/login",
|
|
||||||
query:{redirect:router.currentRoute.value.fullPath}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error('(; ゚Д゚)例外:', err, info);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
0
frontend/src/components/ActionList.vue
Normal file
0
frontend/src/components/ActionList.vue
Normal file
38
frontend/src/components/ActionNode.vue
Normal file
38
frontend/src/components/ActionNode.vue
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-pa-md" style="max-width: 350px">
|
||||||
|
<q-expansion-item
|
||||||
|
class="shadow-1 overflow-hidden"
|
||||||
|
style="border-radius: 30px"
|
||||||
|
icon="explore"
|
||||||
|
label="Counter"
|
||||||
|
@show="startCounting"
|
||||||
|
@hide="stopCounting"
|
||||||
|
header-class="bg-primary text-white"
|
||||||
|
expand-icon-class="text-white"
|
||||||
|
>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
Counting: <q-badge color="secondary">{{ counter }}</q-badge>.
|
||||||
|
Will only count when opened, using the show/hide events to control count timer.
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent} from "vue"
|
||||||
|
export default defineComponent({
|
||||||
|
props:{
|
||||||
|
icon:String,
|
||||||
|
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="">
|
||||||
|
|
||||||
|
</style>
|
||||||
30
frontend/src/components/ActionPanel.vue
Normal file
30
frontend/src/components/ActionPanel.vue
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import {defineComponent,reactive} from 'vue';
|
||||||
|
import {KintoneEvent} from "../models/kintone";
|
||||||
|
export default defineComponent({
|
||||||
|
setup(props, ctx) {
|
||||||
|
const events = reactive<KintoneEvent[]>(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
screen:"レコード追加画面",
|
||||||
|
type:"app.record.create.show",
|
||||||
|
name:"レコード追加画面を表示した後"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
screen:"レコード追加画面",
|
||||||
|
type:"app.record.create.show",
|
||||||
|
name:"レコード追加画面を表示した後"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,133 +1,40 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md">
|
<div class="q-pa-md">
|
||||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
<q-table :title="name+'一覧'" row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
|
||||||
<q-spinner color="primary" size="3em" />
|
|
||||||
</div>
|
|
||||||
<q-splitter
|
|
||||||
v-model="splitterModel"
|
|
||||||
style="height: 100%"
|
|
||||||
before-class="tab"
|
|
||||||
unit="px"
|
|
||||||
v-else
|
|
||||||
>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-tabs
|
|
||||||
v-model="tab"
|
|
||||||
vertical
|
|
||||||
active-color="white"
|
|
||||||
indicator-color="primary"
|
|
||||||
active-bg-color="primary"
|
|
||||||
class="bg-grey-2 text-grey-8"
|
|
||||||
dense
|
|
||||||
>
|
|
||||||
<q-tab :name="cate"
|
|
||||||
:label="cate"
|
|
||||||
v-for="(cate,) in categorys"
|
|
||||||
:key="cate"
|
|
||||||
></q-tab>
|
|
||||||
</q-tabs>
|
|
||||||
</template>
|
|
||||||
<template v-slot:after>
|
|
||||||
<q-table row-key="index" :selection="type" v-model:selected="selected" :columns="columns" :rows="actionForTab"
|
|
||||||
class="action-table"
|
|
||||||
flat bordered
|
|
||||||
virtual-scroll
|
|
||||||
:pagination="pagination"
|
|
||||||
:rows-per-page-options="[0]"
|
|
||||||
:filter="filter"></q-table>
|
|
||||||
</template>
|
|
||||||
</q-splitter>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { ref,onMounted,reactive,watchEffect,computed,watch } from 'vue'
|
import { ref,onMounted,reactive } from 'vue'
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'actionSelect',
|
name: 'actionSelect',
|
||||||
props: {
|
props: {
|
||||||
name: String,
|
name: String,
|
||||||
type: String,
|
type: String
|
||||||
filter:String
|
|
||||||
},
|
},
|
||||||
emits:[
|
setup() {
|
||||||
"clearFilter"
|
|
||||||
],
|
|
||||||
setup(props,{emit}) {
|
|
||||||
const isLoaded=ref(false);
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
|
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
|
||||||
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
|
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
|
||||||
// { name: 'content', label: '内容', field: 'content', sortable: true }
|
{ name: 'content', label: '内容', field: 'content', sortable: true }
|
||||||
];
|
]
|
||||||
const store = useFlowEditorStore();
|
const rows = reactive([])
|
||||||
let actionData =reactive([]);
|
|
||||||
const categorys = ref('');
|
|
||||||
const tab=ref('');
|
|
||||||
const actionForTab=computed(()=>{
|
|
||||||
const rows=[];
|
|
||||||
const actions= props.filter? actionData:actionData.filter(x=>x.categoryname===tab.value);
|
|
||||||
actions.forEach((item,index) =>{
|
|
||||||
rows.push({index,
|
|
||||||
name:item.name,
|
|
||||||
desc:item.title,
|
|
||||||
outputPoints:item.outputpoints,
|
|
||||||
property:item.property});
|
|
||||||
});
|
|
||||||
return rows;
|
|
||||||
});
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
let eventId='';
|
await api.get('http://127.0.0.1:8000/api/kintone/2').then(res =>{
|
||||||
if(store.selectedEvent ){
|
res.data.forEach((item) =>
|
||||||
eventId=store.selectedEvent.header!=='DELETABLE'? store.selectedEvent.eventId : store.selectedEvent.parentId;
|
{
|
||||||
|
rows.push({name:item.name,desc:item.desc,content:item.content});
|
||||||
}
|
}
|
||||||
const res =await api.get(`api/eventactions/${eventId}`);
|
)
|
||||||
actionData= res.data;
|
|
||||||
const categoryNames = Array.from(new Set(actionData.map(x=>x.categoryname)));
|
|
||||||
categorys.value=categoryNames;
|
|
||||||
tab.value = categoryNames.length>0? categoryNames[0]:'';
|
|
||||||
isLoaded.value=true;
|
|
||||||
});
|
});
|
||||||
// watch(props.filter,()=>{
|
|
||||||
// if(props.filter && props.filter!==''){
|
|
||||||
// tab.value='';
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
watch(tab,()=>{
|
|
||||||
if(tab.value!==''){
|
|
||||||
emit('clearFilter','');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
// watchEffect(()=>{
|
|
||||||
// if(props.filter && props.filter!==''){
|
|
||||||
// tab.value='';
|
|
||||||
// }
|
|
||||||
// if(tab.value!==''){
|
|
||||||
// emit('update:filter','');
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
|
rows,
|
||||||
selected: ref([]),
|
selected: ref([]),
|
||||||
pagination:ref({
|
|
||||||
rowsPerPage:0
|
|
||||||
}),
|
|
||||||
isLoaded,
|
|
||||||
tab,
|
|
||||||
actionData,
|
|
||||||
categorys,
|
|
||||||
splitterModel: ref(150),
|
|
||||||
actionForTab
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
|
||||||
.action-table{
|
|
||||||
min-height: 10vh;
|
|
||||||
max-height: 68vh;
|
|
||||||
min-width: 550px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,131 +0,0 @@
|
|||||||
<template>
|
|
||||||
|
|
||||||
<div class="q-mx-md q-mb-lg">
|
|
||||||
<div class="q-mb-xs q-ml-md text-primary">アプリ選択</div>
|
|
||||||
|
|
||||||
<div class="q-pa-md row" style="border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px;">
|
|
||||||
<div v-if="selField?.app && !showSelectApp">{{ selField?.app?.name }}</div>
|
|
||||||
<q-space />
|
|
||||||
<div>
|
|
||||||
<q-btn outline dense label="選 択" padding="none sm" color="primary" @click="() => {
|
|
||||||
showSelectApp = true;
|
|
||||||
}"></q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!showSelectApp && selField?.app?.name">
|
|
||||||
<div>
|
|
||||||
<div class="row q-mb-md">
|
|
||||||
<!-- <div class="col"> -->
|
|
||||||
<div class="q-mb-xs q-ml-md text-primary">フィールド選択</div>
|
|
||||||
<!-- </div> -->
|
|
||||||
<q-space />
|
|
||||||
<!-- <div class="col"> -->
|
|
||||||
<div class="q-mr-md">
|
|
||||||
<q-input dense debounce="300" v-model="fieldFilter" placeholder="フィールド検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelectFields="updateSelectFields"
|
|
||||||
:appId="selField?.app?.id" not_page :filter="fieldFilter"
|
|
||||||
:selectedFields="selField.fields" :fieldTypes="fieldTypes"></field-select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="min-width: 45vw;" v-else>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<show-dialog v-model:visible="showSelectApp" name="アプリ選択">
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<AppSelectBox ref="appDlg" name="アプリ" type="single" :filter="filter"
|
|
||||||
:updateSelectApp="updateSelectApp"></AppSelectBox>
|
|
||||||
</show-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref, watchEffect, computed, reactive } from 'vue';
|
|
||||||
import ShowDialog from './ShowDialog.vue';
|
|
||||||
import FieldSelect from './FieldSelect.vue';
|
|
||||||
import AppSelectBox from './AppSelectBox.vue';
|
|
||||||
interface IApp {
|
|
||||||
id: string,
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
interface IField {
|
|
||||||
name: string,
|
|
||||||
code: string,
|
|
||||||
type: string
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IAppFields {
|
|
||||||
app?: IApp,
|
|
||||||
fields: IField[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
inheritAttrs: false,
|
|
||||||
name: 'AppFieldSelectBox',
|
|
||||||
components: {
|
|
||||||
ShowDialog,
|
|
||||||
FieldSelect,
|
|
||||||
AppSelectBox,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
selectedField: {
|
|
||||||
type: Object,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
selectType: {
|
|
||||||
type: String,
|
|
||||||
default: 'single'
|
|
||||||
},
|
|
||||||
fieldTypes:{
|
|
||||||
type:Array,
|
|
||||||
default:()=>[]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const showSelectApp = ref(false);
|
|
||||||
const selField = reactive(props.selectedField);
|
|
||||||
|
|
||||||
const isSelected = computed(() => {
|
|
||||||
return selField !== null && typeof selField === 'object' && ('app' in selField)
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateSelectApp = (newAppinfo: IApp) => {
|
|
||||||
selField.app = newAppinfo
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateSelectFields = (newFields: IField[]) => {
|
|
||||||
selField.fields = newFields
|
|
||||||
}
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
emit('update:modelValue', selField);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
showSelectApp,
|
|
||||||
isSelected,
|
|
||||||
updateSelectApp,
|
|
||||||
filter: ref(),
|
|
||||||
updateSelectFields,
|
|
||||||
fieldFilter: ref(),
|
|
||||||
selField
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</q-field>
|
</q-field>
|
||||||
<q-field stack-label full-width label="アプリの説明">
|
<q-field stack-label full-width label="アプリ説明">
|
||||||
<template v-slot:control>
|
<template v-slot:control>
|
||||||
<div class="self-center full-width no-outline" tabindex="0">
|
<div class="self-center full-width no-outline" tabindex="0">
|
||||||
{{ appinfo?.description }}
|
{{ appinfo?.description }}
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
import { AppInfo, AppSeed } from './models';
|
import { AppInfo, AppSeed } from './models';
|
||||||
import { ref, defineComponent, watch, onMounted , toRefs } from 'vue';
|
import { ref, defineComponent, watch, onMounted , toRefs } from 'vue';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
import { promises } from 'dns';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -44,13 +44,12 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { app } = toRefs(props);
|
const { app } = toRefs(props);
|
||||||
const authStore = useAuthStore();
|
|
||||||
const appinfo = ref<AppInfo>({
|
const appinfo = ref<AppInfo>({
|
||||||
appId: "",
|
appId: "",
|
||||||
name: "",
|
name: "",
|
||||||
description: ""
|
description: ""
|
||||||
});
|
});
|
||||||
const link= ref(`${authStore.currentDomain.kintoneUrl}/k/${app.value}`);
|
const link= ref('https://mfu07rkgnb7c.cybozu.com/k/' + app.value);
|
||||||
const getAppInfo = async (appId:string|undefined) => {
|
const getAppInfo = async (appId:string|undefined) => {
|
||||||
if(!appId){
|
if(!appId){
|
||||||
return;
|
return;
|
||||||
@@ -60,7 +59,7 @@ export default defineComponent({
|
|||||||
let retry =0;
|
let retry =0;
|
||||||
while(retry<=3 && result && result.appId!==appId){
|
while(retry<=3 && result && result.appId!==appId){
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
const response = await api.get('api/v1/app', {
|
const response = await api.get('app', {
|
||||||
params:{
|
params:{
|
||||||
app: appId
|
app: appId
|
||||||
}
|
}
|
||||||
@@ -74,7 +73,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
watch(app, async (newApp) => {
|
watch(app, async (newApp) => {
|
||||||
appinfo.value = await getAppInfo(newApp);
|
appinfo.value = await getAppInfo(newApp);
|
||||||
link.value = `${authStore.currentDomain.kintoneUrl}/k/${newApp}`;
|
link.value = 'https://mfu07rkgnb7c.cybozu.com/k/' + newApp;
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
const linkClick=(ev : MouseEvent)=>{
|
const linkClick=(ev : MouseEvent)=>{
|
||||||
@@ -83,7 +82,7 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
onMounted(async ()=>{
|
onMounted(async ()=>{
|
||||||
appinfo.value = await getAppInfo(app.value);
|
appinfo.value = await getAppInfo(app.value);
|
||||||
link.value = `${authStore.currentDomain.kintoneUrl}/k/${app.value}`;
|
link.value = 'https://mfu07rkgnb7c.cybozu.com/k/' + app.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -8,25 +8,24 @@ import { ref,onMounted,reactive } from 'vue'
|
|||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DomainSelect',
|
name: 'appSelect',
|
||||||
props: {
|
props: {
|
||||||
name: String,
|
name: String,
|
||||||
type: String
|
type: String
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'id'},
|
{ name: 'id', required: true,label: 'アプリID',align: 'left',field: 'id',sortable: true},
|
||||||
{ name: 'tenantid', required: true,label: 'テナント',align: 'left',field: 'tenantid',sortable: true},
|
{ name: 'name', align: 'center', label: 'アプリ名', field: 'name', sortable: true },
|
||||||
{ name: 'name', align: 'center', label: 'ドメイン', field: 'name', sortable: true },
|
{ name: 'creator', label: '作成者', field: 'creator', sortable: true },
|
||||||
{ name: 'url', label: 'URL', field: 'url', sortable: true },
|
{ name: 'createdate', label: '作成日時', field: 'createdate' }
|
||||||
{ name: 'kintoneuser', label: 'アカウント', field: 'kintoneuser' }
|
|
||||||
]
|
]
|
||||||
const rows = reactive([])
|
const rows = reactive([])
|
||||||
onMounted( () => {
|
onMounted( () => {
|
||||||
api.get(`api/domains/1`).then(res =>{
|
api.get('allapps').then(res =>{
|
||||||
res.data.forEach((item) =>
|
res.data.apps.forEach((item) =>
|
||||||
{
|
{
|
||||||
rows.push({id:item.id,tenantid:item.tenantid,name:item.name,url:item.url,kintoneuser:item.kintoneuser});
|
rows.push({id:item.appId,name:item.name,creator:item.creator.name,createdate:item.createdAt});
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="q-px-xs">
|
|
||||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
|
||||||
<q-spinner color="primary" size="3em" />
|
|
||||||
</div>
|
|
||||||
<q-table v-else class="app-table" :selection="type" row-key="id" v-model:selected="selected" flat bordered
|
|
||||||
virtual-scroll :columns="columns" :rows="rows" :pagination="pagination" :rows-per-page-options="[0]"
|
|
||||||
:filter="filter" style="max-height: 65vh;">
|
|
||||||
<template v-slot:body-cell-description="props">
|
|
||||||
<q-td :props="props">
|
|
||||||
<q-scroll-area class="description-cell">
|
|
||||||
<div v-html="props.row.description"></div>
|
|
||||||
</q-scroll-area>
|
|
||||||
</q-td>
|
|
||||||
</template>
|
|
||||||
</q-table>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { ref, onMounted, reactive, watchEffect, PropType } from 'vue'
|
|
||||||
import { api } from 'boot/axios';
|
|
||||||
|
|
||||||
interface IAppDisplay {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
createdate: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'AppSelectBox',
|
|
||||||
props: {
|
|
||||||
name: String,
|
|
||||||
type: String,
|
|
||||||
filter: String,
|
|
||||||
filterInitRowsFunc: {
|
|
||||||
type: Function as PropType<(app: IAppDisplay) => boolean>,
|
|
||||||
},
|
|
||||||
updateSelectApp: {
|
|
||||||
type: Function
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const columns = [
|
|
||||||
{ name: 'id', required: true, label: 'ID', align: 'left', field: 'id', sortable: true, sort: (a: string, b: string) => parseInt(a, 10) - parseInt(b, 10) },
|
|
||||||
{ name: 'name', label: 'アプリ名', field: 'name', sortable: true, align: 'left' },
|
|
||||||
{ name: 'description', label: '概要', field: 'description', align: 'left', sortable: false },
|
|
||||||
{ name: 'createdate', label: '作成日時', field: 'createdate', align: 'left' }
|
|
||||||
]
|
|
||||||
const isLoaded = ref(false);
|
|
||||||
const rows = reactive<IAppDisplay[]>([]);
|
|
||||||
const selected = ref([])
|
|
||||||
|
|
||||||
watchEffect(()=>{
|
|
||||||
if (selected.value && selected.value[0] && props.updateSelectApp) {
|
|
||||||
props.updateSelectApp(selected.value[0])
|
|
||||||
}
|
|
||||||
});
|
|
||||||
onMounted(() => {
|
|
||||||
api.get('api/v1/allapps').then(res => {
|
|
||||||
res.data.apps.forEach((item: any) => {
|
|
||||||
const row : IAppDisplay = {
|
|
||||||
id: item.appId,
|
|
||||||
name: item.name,
|
|
||||||
description: item.description,
|
|
||||||
createdate: dateFormat(item.createdAt)
|
|
||||||
}
|
|
||||||
if (props.filterInitRowsFunc && !props.filterInitRowsFunc(row)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rows.push(row);
|
|
||||||
});
|
|
||||||
isLoaded.value = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const dateFormat = (dateStr: string) => {
|
|
||||||
const date = new Date(dateStr);
|
|
||||||
const pad = (num: number) => num.toString().padStart(2, '0');
|
|
||||||
const year = date.getFullYear();
|
|
||||||
const month = pad(date.getMonth() + 1);
|
|
||||||
const day = pad(date.getDate());
|
|
||||||
const hours = pad(date.getHours());
|
|
||||||
const minutes = pad(date.getMinutes());
|
|
||||||
const seconds = pad(date.getSeconds());
|
|
||||||
return `${year}/${month}/${day} ${hours}:${minutes}:${seconds}`;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
columns,
|
|
||||||
rows,
|
|
||||||
selected,
|
|
||||||
isLoaded,
|
|
||||||
pagination: ref({
|
|
||||||
rowsPerPage: 10
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
.description-cell {
|
|
||||||
height: 60px;
|
|
||||||
width: 300px;
|
|
||||||
max-height: 60px;
|
|
||||||
max-width: 300px;
|
|
||||||
white-space: break-spaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
min-height: 300px;
|
|
||||||
min-width: 400px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,271 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<q-stepper v-model="step" ref="stepper" color="primary" animated flat>
|
|
||||||
<q-step :name="1" title="データソースの設定" icon="app_registration" :done="step > 1">
|
|
||||||
<div class="row justify-between items-center">
|
|
||||||
<div>アプリの選択 :</div>
|
|
||||||
<div>
|
|
||||||
<a v-if="data.sourceApp?.name" class="q-mr-xs"
|
|
||||||
:href="data.sourceApp ? `${authStore.currentDomain.kintoneUrl}/k/${data.sourceApp.id}` : ''"
|
|
||||||
target="_blank" title="Kiontoneへ">
|
|
||||||
{{ data.sourceApp?.name }}
|
|
||||||
</a>
|
|
||||||
<div v-else class="text-red">APPを選択してください</div>
|
|
||||||
<q-btn v-if="data.sourceApp?.name" flat color="grey" icon="clear" size="sm" padding="none"
|
|
||||||
@click="clearSelectedApp" />
|
|
||||||
</div>
|
|
||||||
<q-btn outline dense label="変更" padding="xs sm" color="primary" @click="showAppDialog" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- フィールド設定部分 -->
|
|
||||||
<template v-if="data.sourceApp?.name">
|
|
||||||
<q-separator class="q-mt-md" />
|
|
||||||
<div class="q-my-md row justify-between items-center">
|
|
||||||
データ階層を設定する :
|
|
||||||
<q-btn icon="add" size="sm" padding="xs" outline color="primary" @click="addRow" />
|
|
||||||
</div>
|
|
||||||
<q-virtual-scroll style="max-height: 13.5rem;" :items="data.fieldList" separator v-slot="{ item, index }">
|
|
||||||
<div class="row justify-between items-center q-my-md">
|
|
||||||
<div>{{ index + 1 }}階層 :</div>
|
|
||||||
<div>{{ item.source?.name }}</div>
|
|
||||||
<q-btn-group outline>
|
|
||||||
<q-btn outline dense label="変更" padding="xs sm" color="primary"
|
|
||||||
@click="() => showFieldDialog(item, 'source')" />
|
|
||||||
<q-btn outline dense label="削除" padding="xs sm" color="primary" @click="() => delRow(index)" />
|
|
||||||
</q-btn-group>
|
|
||||||
</div>
|
|
||||||
</q-virtual-scroll>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- アプリ選択ダイアログ -->
|
|
||||||
<ShowDialog v-model:visible="data.sourceApp.showSelectApp" name="アプリ選択" @close="closeAppDialog" min-width="50vw"
|
|
||||||
min-height="50vh">
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-input dense debounce="300" v-model="data.sourceApp.appFilter" placeholder="検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="data.sourceApp.appFilter" />
|
|
||||||
</ShowDialog>
|
|
||||||
</q-step>
|
|
||||||
|
|
||||||
<q-step :name="2" title="ドロップダウンフィールドの設定" icon="multiple_stop" :done="step > 2">
|
|
||||||
<div class="row q-pa-sm q-col-gutter-x-sm flex-center">
|
|
||||||
<div class="col-grow row q-col-gutter-x-sm">
|
|
||||||
<div class="col-6">データソース</div>
|
|
||||||
<div class="col-6">ドロップダウンフィールド</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<div style="width: 88px; height: 1px;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-for="(item) in data.fieldList" :key="item.id" class="row q-pa-sm q-col-gutter-x-sm flex-center">
|
|
||||||
<div class="col-grow row q-col-gutter-x-sm">
|
|
||||||
|
|
||||||
<div class="col-6">{{ item.source.name }}</div>
|
|
||||||
<div class="col-6">{{ item.dropDown?.name }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<div class="row justify-end">
|
|
||||||
<q-btn-group outline>
|
|
||||||
<q-btn outline dense label="設定" padding="xs sm" color="primary"
|
|
||||||
@click="() => showFieldDialog(item, 'dropDown')" />
|
|
||||||
<q-btn outline dense label="クリア" padding="xs sm" color="primary"
|
|
||||||
@click="() => item.dropDown = undefined" />
|
|
||||||
</q-btn-group>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-step>
|
|
||||||
|
|
||||||
<!-- ステップナビゲーション -->
|
|
||||||
<template v-slot:navigation>
|
|
||||||
<q-stepper-navigation>
|
|
||||||
<div class="row justify-end q-mt-md">
|
|
||||||
<q-btn v-if="step > 1" flat color="primary" @click="$refs.stepper.previous()" label="戻る" class="q-ml-sm" />
|
|
||||||
<q-btn @click="stepperNext" color="primary" :label="step === 2 ? '確定' : '次へ'"
|
|
||||||
:disable="nextBtnCheck()" />
|
|
||||||
</div>
|
|
||||||
</q-stepper-navigation>
|
|
||||||
</template>
|
|
||||||
</q-stepper>
|
|
||||||
|
|
||||||
<!-- フィールド選択ダイアログ -->
|
|
||||||
<template v-for="(item, index) in data.fieldList" :key="`dg${item.id}`">
|
|
||||||
<show-dialog v-model:visible="item.sourceDg.show" name="フィールド一覧" min-width="400px">
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-input dense debounce="300" v-model="item.sourceDg.filter" placeholder="検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
<FieldSelect name="フィールド" :appId="data.sourceApp.id" :selectedFields="item.source"
|
|
||||||
:filter="item.sourceDg.filter" :updateSelectFields="(f) => updateSelectField(f, item, index, 'source')"
|
|
||||||
:blackListLabel="blackListLabel" />
|
|
||||||
</show-dialog>
|
|
||||||
|
|
||||||
<show-dialog v-model:visible="item.dropDownDg.show" name="フィールド一覧" min-width="400px">
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-input dense debounce="300" v-model="item.dropDownDg.filter" placeholder="検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
<FieldSelect name="フィールド" :appId="data.dropDownApp.id" :selectedFields="item.source"
|
|
||||||
:filter="item.dropDownDg.filter" :updateSelectFields="(f) => updateSelectField(f, item, index, 'dropDown')"
|
|
||||||
:blackListLabel="blackListLabel" />
|
|
||||||
</show-dialog>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, reactive, ref, watchEffect,watch } from 'vue';
|
|
||||||
import ShowDialog from './ShowDialog.vue';
|
|
||||||
import AppSelectBox from './AppSelectBox.vue';
|
|
||||||
import FieldSelect from './FieldSelect.vue';
|
|
||||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
|
||||||
import { useFlowEditorStore } from 'src/stores/flowEditor';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { useQuasar } from 'quasar';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'CascadingDropDownBox',
|
|
||||||
inheritAttrs: false,
|
|
||||||
components: { ShowDialog, AppSelectBox, FieldSelect },
|
|
||||||
props: {
|
|
||||||
modelValue: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
finishDialogHandler: Function,
|
|
||||||
},
|
|
||||||
emits: ['update:modelValue'],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
const flowStore = useFlowEditorStore();
|
|
||||||
const $q = useQuasar();
|
|
||||||
const appDg = ref();
|
|
||||||
const stepper = ref();
|
|
||||||
const step = ref(1);
|
|
||||||
|
|
||||||
const data =ref(props.modelValue);
|
|
||||||
// const data = ref({
|
|
||||||
// sourceApp: props.modelValue.sourceApp ?? { appFilter: '', showSelectApp: false },
|
|
||||||
// dropDownApp: props.modelValue.dropDownApp,
|
|
||||||
// fieldList: props.modelValue.fieldList ?? [],
|
|
||||||
// });
|
|
||||||
|
|
||||||
// アプリ関連の関数
|
|
||||||
const showAppDialog = () => data.value.sourceApp.showSelectApp = true;
|
|
||||||
|
|
||||||
const clearSelectedApp = () => {
|
|
||||||
data.value.sourceApp = { appFilter: '', showSelectApp: false };
|
|
||||||
data.value.fieldList = [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeAppDialog = (val: 'OK' | 'Cancel') => {
|
|
||||||
data.value.sourceApp.showSelectApp = false;
|
|
||||||
const selected = appDg.value?.selected[0];
|
|
||||||
if (val === 'OK' && selected) {
|
|
||||||
if (flowStore.appInfo?.appId === selected.id) {
|
|
||||||
$q.notify({
|
|
||||||
type: 'negative',
|
|
||||||
caption: "エラー",
|
|
||||||
message: 'データソースを現在のアプリにすることはできません。'
|
|
||||||
});
|
|
||||||
} else if (selected.id !== data.value.sourceApp.id) {
|
|
||||||
clearSelectedApp();
|
|
||||||
Object.assign(data.value.sourceApp, { id: selected.id, name: selected.name });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// フィールド関連の関数
|
|
||||||
const defaultRow = () => ({
|
|
||||||
id: uuidv4(),
|
|
||||||
source: undefined,
|
|
||||||
dropDown: undefined,
|
|
||||||
sourceDg: { show: false, filter: '' },
|
|
||||||
dropDownDg: { show: false, filter: '' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const addRow = () => data.value.fieldList.push(defaultRow());
|
|
||||||
const delRow = (index: number) => data.value.fieldList.splice(index, 1);
|
|
||||||
|
|
||||||
const showFieldDialog = (item: any, keyName: string) => item[`${keyName}Dg`].show = true;
|
|
||||||
|
|
||||||
const updateSelectField = (f: any, item: any, index: number, keyName: 'source' | 'dropDown') => {
|
|
||||||
const [selected] = f.value;
|
|
||||||
const isDuplicate = data.value.fieldList.some((field, idx) =>
|
|
||||||
idx !== index && (field[keyName]?.code === selected.code || field[keyName]?.label === selected.label)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isDuplicate) {
|
|
||||||
$q.notify({
|
|
||||||
type: 'negative',
|
|
||||||
caption: "エラー",
|
|
||||||
message: '重複したフィールドは選択できません'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
item[keyName] = selected;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ステッパー関連の関数
|
|
||||||
const nextBtnCheck = () => {
|
|
||||||
const stepNo = step.value
|
|
||||||
if (stepNo === 1) {
|
|
||||||
return !(data.value.sourceApp?.id && data.value.fieldList?.length > 0 && data.value.fieldList?.every(f => f.source?.name));
|
|
||||||
} else if (stepNo === 2) {
|
|
||||||
return !data.value.fieldList?.every(f => f.dropDown?.name);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const stepperNext = () => {
|
|
||||||
if (step.value === 2) {
|
|
||||||
props.finishDialogHandler?.(data.value);
|
|
||||||
} else {
|
|
||||||
data.value.dropDownApp = { name: flowStore.appInfo?.name, id: flowStore.appInfo?.appId };
|
|
||||||
stepper.value?.next();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// // データ変更の監視
|
|
||||||
// watchEffect(() =>{
|
|
||||||
// emit('update:modelValue', data.value);
|
|
||||||
// });
|
|
||||||
|
|
||||||
return {
|
|
||||||
// 状態と参照
|
|
||||||
authStore,
|
|
||||||
step,
|
|
||||||
stepper,
|
|
||||||
appDg,
|
|
||||||
data,
|
|
||||||
// アプリ関連の関数
|
|
||||||
showAppDialog,
|
|
||||||
closeAppDialog,
|
|
||||||
clearSelectedApp,
|
|
||||||
|
|
||||||
// フィールド関連の関数
|
|
||||||
addRow,
|
|
||||||
delRow,
|
|
||||||
showFieldDialog,
|
|
||||||
updateSelectField,
|
|
||||||
|
|
||||||
// ステッパー関連の関数
|
|
||||||
nextBtnCheck,
|
|
||||||
stepperNext,
|
|
||||||
|
|
||||||
// 定数
|
|
||||||
blackListLabel: ['レコード番号', '作業者', '更新者', '更新日時', '作成日時', '作成者', 'カテゴリー', 'ステータス'],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
<template>
|
|
||||||
<show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" min-width="50vw" min-height="60vh">
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-btn flat round dense icon="more_vert" >
|
|
||||||
<q-menu auto-close anchor="bottom start">
|
|
||||||
<q-list>
|
|
||||||
<q-item clickable @click="copyCondition()">
|
|
||||||
<q-item-section avatar><q-icon name="content_copy" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >コピー</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable @click="pasteCondition()">
|
|
||||||
<q-item-section avatar><q-icon name="content_paste" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >貼り付け</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-menu>
|
|
||||||
</q-btn>
|
|
||||||
</template>
|
|
||||||
<NodeCondition v-model:conditionTree="tree"></NodeCondition>
|
|
||||||
</show-dialog>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref ,watchEffect} from 'vue';
|
|
||||||
import ShowDialog from '../../components/ShowDialog.vue';
|
|
||||||
import NodeCondition from './NodeCondition.vue';
|
|
||||||
import { ConditionTree } from '../../types/Conditions';
|
|
||||||
import { useQuasar } from 'quasar';
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ConditionObject',
|
|
||||||
components: {
|
|
||||||
ShowDialog,
|
|
||||||
NodeCondition,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
conditionTree: {
|
|
||||||
type: ConditionTree,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
show:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emits:[
|
|
||||||
"closed",
|
|
||||||
"update:conditionTree",
|
|
||||||
"update:show"
|
|
||||||
],
|
|
||||||
setup(props,context) {
|
|
||||||
const appDg = ref();
|
|
||||||
const $q=useQuasar();
|
|
||||||
const tree = ref(props.conditionTree);
|
|
||||||
const closeDg = (val:string) => {
|
|
||||||
if (val == 'OK') {
|
|
||||||
// if(tree.value.root.children.length===0){
|
|
||||||
// $q.notify({
|
|
||||||
// type: 'negative',
|
|
||||||
// message: `条件式を設定してください。`
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
context.emit("update:conditionTree",tree.value);
|
|
||||||
}
|
|
||||||
showflg.value=false;
|
|
||||||
context.emit("update:show",false);
|
|
||||||
context.emit("closed",val);
|
|
||||||
};
|
|
||||||
const showflg =ref(props.show);
|
|
||||||
//条件式をコピーする
|
|
||||||
const copyCondition=()=>{
|
|
||||||
if (navigator.clipboard) {
|
|
||||||
const jsonData=tree.value.toJson();
|
|
||||||
navigator.clipboard.writeText(jsonData).then(() => {
|
|
||||||
console.log('Text successfully copied to clipboard');
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.error('Error in copying text: ', err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('Clipboard API not available');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//条件式を貼り付ける
|
|
||||||
const pasteCondition=async ()=>{
|
|
||||||
try {
|
|
||||||
const text = await navigator.clipboard.readText();
|
|
||||||
console.log('Text from clipboard:', text);
|
|
||||||
tree.value.fromJson(text);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to read text from clipboard: ', err);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
watchEffect(() => {
|
|
||||||
showflg.value=props.show;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
tree,
|
|
||||||
appDg,
|
|
||||||
closeDg,
|
|
||||||
showflg,
|
|
||||||
copyCondition,
|
|
||||||
pasteCondition
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-field labelColor="primary" class="condition-object" dense outlined :label="label" :disable="disabled"
|
|
||||||
:clearable="isSelected">
|
|
||||||
<template v-slot:control>
|
|
||||||
<q-chip color="primary" text-color="white" v-if="isSelected && selectedObject.objectType==='field'" :dense="true" class="selected-obj">
|
|
||||||
{{ selectedObject.name }}
|
|
||||||
</q-chip>
|
|
||||||
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
|
|
||||||
{{ selectedObject.name.name }}
|
|
||||||
</q-chip>
|
|
||||||
<div v-if="isSelected && selectedObject.objectType==='text'">{{ selectedObject?.sharedText }}</div>
|
|
||||||
</template>
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon name="search" class="cursor-pointer" @click="showDg" />
|
|
||||||
</template>
|
|
||||||
</q-field>
|
|
||||||
<show-dialog v-model:visible="show" name="設定項目" @close="closeDg" min-width="400px">
|
|
||||||
<!-- <template v-slot:toolbar>
|
|
||||||
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
|
|
||||||
-->
|
|
||||||
<DynamicItemInput v-model:selectedObject="selectedObject" :canInput="config.canInput"
|
|
||||||
:buttonsConfig="config.buttonsConfig" :appId="store.appInfo?.appId" :options="options" ref="inputRef" />
|
|
||||||
|
|
||||||
</show-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, reactive, ref, watchEffect, computed ,PropType} from 'vue';
|
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
|
||||||
// import ConditionObjects from '../ConditionObjects.vue';
|
|
||||||
import DynamicItemInput from '../DynamicItemInput/DynamicItemInput.vue';
|
|
||||||
import { useFlowEditorStore } from '../../stores/flowEditor';
|
|
||||||
import { IActionFlow, IActionNode, IActionVariable } from '../../types/ActionTypes';
|
|
||||||
import { IDynamicInputConfig } from 'src/types/ComponentTypes';
|
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ConditionObject',
|
|
||||||
components: {
|
|
||||||
ShowDialog,
|
|
||||||
DynamicItemInput,
|
|
||||||
// ConditionObjects
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
label: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
config: {
|
|
||||||
type: Object as PropType<IDynamicInputConfig>,
|
|
||||||
default: () => {
|
|
||||||
return {
|
|
||||||
canInput: false,
|
|
||||||
buttonsConfig: [
|
|
||||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
|
||||||
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
|
||||||
]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
options:
|
|
||||||
{
|
|
||||||
type:Array as PropType< string[]>,
|
|
||||||
default:()=>[]
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: Object,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
// const appDg = ref();
|
|
||||||
const inputRef=ref();
|
|
||||||
const show = ref(false);
|
|
||||||
const selectedObject = ref(props.modelValue);
|
|
||||||
const store = useFlowEditorStore();
|
|
||||||
// const sharedText = ref(''); // 共享的文本状态
|
|
||||||
const isSelected = computed(() => {
|
|
||||||
return selectedObject.value?.sharedText !== '';
|
|
||||||
});
|
|
||||||
// const isSelected = computed(()=>{
|
|
||||||
// return selectedObject.value!==null && typeof selectedObject.value === 'object' && ('name' in selectedObject.value)
|
|
||||||
// });
|
|
||||||
let vars: IActionVariable[] = [];
|
|
||||||
if (store.currentFlow !== undefined && store.activeNode !== undefined) {
|
|
||||||
vars = store.currentFlow.getVarNames(store.activeNode);
|
|
||||||
}
|
|
||||||
// const filter=ref('');
|
|
||||||
const showDg = () => {
|
|
||||||
show.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeDg = (val: string) => {
|
|
||||||
if (val == 'OK') {
|
|
||||||
// selectedObject.value = appDg.value.selected[0];
|
|
||||||
selectedObject.value = inputRef.value.selectedObjectRef
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
emit('update:modelValue', selectedObject.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
inputRef,
|
|
||||||
store,
|
|
||||||
// appDg,
|
|
||||||
show,
|
|
||||||
showDg,
|
|
||||||
closeDg,
|
|
||||||
selectedObject,
|
|
||||||
vars: reactive(vars),
|
|
||||||
isSelected,
|
|
||||||
buttonsConfig: [
|
|
||||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
|
||||||
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
|
||||||
]
|
|
||||||
// filter
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
.condition-object {
|
|
||||||
min-width: 200px;
|
|
||||||
max-height: 40px;
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-obj {
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
<template>
|
|
||||||
<!-- <q-toolbar class="bg-grey-3" flat dense round icon="menu" aria-label="Menu" @click.stop>
|
|
||||||
<q-toolbar-title>条件エディタ</q-toolbar-title>
|
|
||||||
<q-space></q-space>
|
|
||||||
<q-btn flat round dense icon="info" color="blue" @click="showingCondition=!showingCondition"></q-btn>
|
|
||||||
</q-toolbar> -->
|
|
||||||
<div class="q-pa-md">
|
|
||||||
<q-tree :nodes="[tree.root]" node-key="index" children-key="children"
|
|
||||||
tick-strategy="strict" v-model:ticked="ticked" :expanded="expanded" default-expand-all dense color="primary" >
|
|
||||||
<template v-slot:header-root="prop">
|
|
||||||
<!-- root -->
|
|
||||||
<div class="row items-center" @click.stop>
|
|
||||||
<q-select v-model="prop.node.logicalOperator" :options="logicalOperators" filled outlined dense></q-select>
|
|
||||||
<q-btn flat round dense icon="more_horiz" size="sm" >
|
|
||||||
<q-menu auto-close anchor="top right">
|
|
||||||
<q-list>
|
|
||||||
<q-item clickable @click="addGroup(prop.node, LogicalOperator.AND)">
|
|
||||||
<q-item-section avatar><q-icon name="playlist_add" ></q-icon></q-item-section>
|
|
||||||
<q-item-section>グループの追加</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable @click="addCondition(prop.node)">
|
|
||||||
<q-item-section avatar><q-icon name="add_circle_outline" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >条件式の追加</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-menu>
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-slot:header-generic="prop">
|
|
||||||
<!-- logic group -->
|
|
||||||
<div v-if="prop.node.type !== NodeType.Condition" class="row items-center" @click.stop>
|
|
||||||
<q-select v-model="prop.node.logicalOperator" :options="logicalOperators" :outlined="true" :filled="true" :dense="true"></q-select>
|
|
||||||
<q-btn flat round dense icon="more_horiz" size="sm" >
|
|
||||||
<q-menu auto-close anchor="top right">
|
|
||||||
<q-list>
|
|
||||||
<q-item clickable @click="moveUp(prop.node)">
|
|
||||||
<q-item-section avatar><q-icon name="arrow_upward" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >一つ上に移動</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable @click="moveDown(prop.node)">
|
|
||||||
<q-item-section avatar><q-icon name="arrow_downward" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >一つ下に移動</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-separator inset/>
|
|
||||||
<q-item clickable @click="addGroup(prop.node, LogicalOperator.AND)">
|
|
||||||
<q-item-section avatar><q-icon name="playlist_add" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >グループ追加</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable @click="addCondition(prop.node)">
|
|
||||||
<q-item-section avatar><q-icon name="add_circle_outline" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >条件式追加</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-separator inset/>
|
|
||||||
<q-item clickable @click="splitGroup(prop.node)">
|
|
||||||
<q-item-section avatar><q-icon name="playlist_remove" color="negative"></q-icon></q-item-section>
|
|
||||||
<q-item-section >グループ化解除</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable @click="removeNode(prop.node)">
|
|
||||||
<q-item-section avatar><q-icon name="delete" color="negative"></q-icon></q-item-section>
|
|
||||||
<q-item-section >削除</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-menu>
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
<!-- condition -->
|
|
||||||
<div @click.stop @keypress.stop v-else >
|
|
||||||
<div class="row no-wrap items-center q-my-xs">
|
|
||||||
<ConditionObject v-bind="prop.node" v-model="prop.node.object" :config="leftDynamicItemConfig" class="col-4"/>
|
|
||||||
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
|
|
||||||
<ConditionObject v-bind="prop.node" v-model="prop.node.value" :config="rightDynamicItemConfig" class="col-4"
|
|
||||||
:options="objectValueOptions(prop.node?.object?.options)"
|
|
||||||
/>
|
|
||||||
<!-- <ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"/> -->
|
|
||||||
<!-- <q-input v-if="!prop.node.object || !('options' in prop.node.object)"
|
|
||||||
v-model="prop.node.value"
|
|
||||||
class="condition-value" :outlined="true" :dense="true" ></q-input> -->
|
|
||||||
<!-- <q-select v-if="prop.node.object && ('options' in prop.node.object)"
|
|
||||||
v-model="prop.node.value"
|
|
||||||
:options="objectValueOptions(prop.node.object.options)"
|
|
||||||
clearable
|
|
||||||
value-key="index"
|
|
||||||
class="condition-value" :outlined="true" :dense="true" ></q-select> -->
|
|
||||||
<q-btn flat round dense icon="more_horiz" size="sm" >
|
|
||||||
<q-menu auto-close anchor="top right">
|
|
||||||
<q-list>
|
|
||||||
<q-item clickable @click="moveUp(prop.node)">
|
|
||||||
<q-item-section avatar><q-icon name="arrow_upward" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >一つ上に移動</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable @click="moveDown(prop.node)">
|
|
||||||
<q-item-section avatar><q-icon name="arrow_downward" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >一つ下に移動</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-separator inset/>
|
|
||||||
<q-item clickable @click="groupMerge(prop.node)" v-if="canMerge(prop.node)">
|
|
||||||
<q-item-section avatar><q-icon name="playlist_add"></q-icon></q-item-section>
|
|
||||||
<q-item-section >グループ化</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-separator inset/>
|
|
||||||
<q-item clickable @click="removeNode(prop.node)">
|
|
||||||
<q-item-section avatar><q-icon name="delete" color="negative"></q-icon></q-item-section>
|
|
||||||
<q-item-section>削除</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-menu>
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</q-tree>
|
|
||||||
<q-tooltip anchor="center middle" v-model="showingCondition" no-parent-event>
|
|
||||||
import { finished } from 'stream';
|
|
||||||
{{ conditionString }}
|
|
||||||
</q-tooltip>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent,ref,reactive, computed, inject } from 'vue';
|
|
||||||
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
|
|
||||||
import ConditionObject from './ConditionObject.vue';
|
|
||||||
import { IDynamicInputConfig } from 'src/types/ComponentTypes';
|
|
||||||
export default defineComponent( {
|
|
||||||
name: 'NodeCondition',
|
|
||||||
components: {
|
|
||||||
ConditionObject
|
|
||||||
},
|
|
||||||
props:{
|
|
||||||
conditionTree: {
|
|
||||||
type: ConditionTree,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
show:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const ticked= ref([]);
|
|
||||||
const showingCondition=ref(false);
|
|
||||||
|
|
||||||
const logicalOperators = computed(()=>{
|
|
||||||
const opts=[];
|
|
||||||
for(const op in LogicalOperator){
|
|
||||||
opts.push(LogicalOperator[op as keyof typeof LogicalOperator]);
|
|
||||||
}
|
|
||||||
return opts;
|
|
||||||
});
|
|
||||||
|
|
||||||
const operatorSet = inject<Array<any>>('Operator')
|
|
||||||
const operators = ref(operatorSet ? operatorSet : Object.values(Operator));
|
|
||||||
const tree = reactive(props.conditionTree);
|
|
||||||
|
|
||||||
const conditionString = computed(()=>{
|
|
||||||
return tree.buildConditionString(tree.root);
|
|
||||||
});
|
|
||||||
|
|
||||||
const objectValueOptions=(options:any):any[]|null=>{
|
|
||||||
if(!options){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const opts:any[] =[];
|
|
||||||
Object.keys(options).forEach((key) =>
|
|
||||||
{
|
|
||||||
const opt=options[key];
|
|
||||||
opts.push(opt);
|
|
||||||
});
|
|
||||||
return opts;
|
|
||||||
};
|
|
||||||
|
|
||||||
const addGroup = (parent:GroupNode, logicOp:LogicalOperator) => {
|
|
||||||
if(!parent){
|
|
||||||
parent=tree.root;
|
|
||||||
}
|
|
||||||
tree.addNode(parent,new GroupNode(logicOp,parent));
|
|
||||||
};
|
|
||||||
|
|
||||||
const addCondition = (parent:GroupNode) => {
|
|
||||||
const newNode = new ConditionNode({},Operator.Equal,'',parent);
|
|
||||||
tree.addNode(parent,newNode);
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeNode = (node:INode) => {
|
|
||||||
tree.removeNode(node);
|
|
||||||
};
|
|
||||||
|
|
||||||
const moveUp =(node:INode)=>{
|
|
||||||
tree.moveNode(node,'up');
|
|
||||||
}
|
|
||||||
|
|
||||||
const moveDown =(node:INode)=>{
|
|
||||||
tree.moveNode(node,'down');
|
|
||||||
}
|
|
||||||
|
|
||||||
const getConditionJson=()=>{
|
|
||||||
return tree.toJson();
|
|
||||||
}
|
|
||||||
//JsonからConditionTreeのインスタンスを作成
|
|
||||||
const LoadCondition=()=>{
|
|
||||||
tree.fromJson(conditionString.value);
|
|
||||||
}
|
|
||||||
//グループ化
|
|
||||||
const groupMerge=(node:INode)=>{
|
|
||||||
const checkedNodes:INode[]=[];
|
|
||||||
const checkedIndexs:number[] = ticked.value;
|
|
||||||
checkedIndexs.forEach(index => {
|
|
||||||
const node = tree.findByIndex(index);
|
|
||||||
if(node){
|
|
||||||
checkedNodes.push(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tree.createGroupNode(node,checkedNodes,LogicalOperator.AND);
|
|
||||||
ticked.value=[];
|
|
||||||
}
|
|
||||||
//グループ化可能かをチェックする
|
|
||||||
const canMerge =(node:INode)=>{
|
|
||||||
const checkedIndexs:number[] = ticked.value;
|
|
||||||
const findNode = checkedIndexs.find(index=>node.index===index);
|
|
||||||
console.log("findNode=>",findNode!==undefined,findNode);
|
|
||||||
return findNode!==undefined;
|
|
||||||
}
|
|
||||||
//グループ化解散
|
|
||||||
const splitGroup=(node:INode)=>{
|
|
||||||
tree.dissolveGroupNode(node as GroupNode);
|
|
||||||
ticked.value=[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const expanded=computed(()=>tree.getGroups(tree.root));
|
|
||||||
// addCondition(tree.root);
|
|
||||||
const leftDynamicItemConfig = inject<IDynamicInputConfig>('leftDynamicItemConfig');
|
|
||||||
const rightDynamicItemConfig = inject<IDynamicInputConfig>('rightDynamicItemConfig');
|
|
||||||
|
|
||||||
return {
|
|
||||||
leftDynamicItemConfig,
|
|
||||||
rightDynamicItemConfig,
|
|
||||||
showingCondition,
|
|
||||||
conditionString,
|
|
||||||
tree,
|
|
||||||
ticked,
|
|
||||||
logicalOperators,
|
|
||||||
operators,
|
|
||||||
addGroup,
|
|
||||||
addCondition,
|
|
||||||
removeNode,
|
|
||||||
moveUp,
|
|
||||||
moveDown,
|
|
||||||
LogicalOperator,
|
|
||||||
Operator,
|
|
||||||
NodeType,
|
|
||||||
getConditionJson,
|
|
||||||
LoadCondition,
|
|
||||||
objectValueOptions,
|
|
||||||
expanded,
|
|
||||||
canMerge,
|
|
||||||
groupMerge,
|
|
||||||
splitGroup
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
.condition-value{
|
|
||||||
min-width: 200px;
|
|
||||||
max-height: 40px;
|
|
||||||
margin: 0 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.operator{
|
|
||||||
min-width: 150px;
|
|
||||||
max-height: 40px;
|
|
||||||
margin: 0 2px;
|
|
||||||
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12pt;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="q-gutter-y-md" style="max-width: 600px;">
|
|
||||||
<q-card >
|
|
||||||
<q-tabs
|
|
||||||
v-model="tab"
|
|
||||||
dense
|
|
||||||
class="text-grey"
|
|
||||||
active-color="white"
|
|
||||||
active-bg-color="primary"
|
|
||||||
indicator-color="primary"
|
|
||||||
align="justify"
|
|
||||||
narrow-indicator
|
|
||||||
>
|
|
||||||
<q-tab name="fields" label="フィールド"></q-tab>
|
|
||||||
<q-tab name="vars" label="変数"></q-tab>
|
|
||||||
</q-tabs>
|
|
||||||
|
|
||||||
<q-separator></q-separator>
|
|
||||||
|
|
||||||
<q-tab-panels v-model="tab" animated>
|
|
||||||
<q-tab-panel name="fields">
|
|
||||||
<field-list v-model="selected" type="single" :filter="filter" :appId="sourceApp ? sourceApp :appId " :fields="sourceFields"></field-list>
|
|
||||||
</q-tab-panel>
|
|
||||||
|
|
||||||
<q-tab-panel name="vars" >
|
|
||||||
<variable-list v-model="selected" type="single" :vars="vars"></variable-list>
|
|
||||||
</q-tab-panel>
|
|
||||||
</q-tab-panels>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { ref, onMounted, reactive, inject } from 'vue'
|
|
||||||
import FieldList from './FieldList.vue';
|
|
||||||
import VariableList from './VariableList.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ConditionObjects',
|
|
||||||
components:{
|
|
||||||
FieldList,
|
|
||||||
VariableList
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
name: String,
|
|
||||||
type: String,
|
|
||||||
appId: Number,
|
|
||||||
vars: Array,
|
|
||||||
filter:String
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const selected = ref([]);
|
|
||||||
console.log(selected);
|
|
||||||
|
|
||||||
return {
|
|
||||||
sourceFields : inject('sourceFields'),
|
|
||||||
sourceApp : inject('sourceApp'),
|
|
||||||
tab: ref('fields'),
|
|
||||||
selected
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -4,8 +4,7 @@
|
|||||||
style="max-width: 400px"
|
style="max-width: 400px"
|
||||||
:url="uploadUrl"
|
:url="uploadUrl"
|
||||||
:label="title"
|
:label="title"
|
||||||
:headers="headers"
|
accept=".csv,.xlsx"
|
||||||
accept=".xlsx"
|
|
||||||
v-on:rejected="onRejected"
|
v-on:rejected="onRejected"
|
||||||
v-on:uploaded="onUploadFinished"
|
v-on:uploaded="onUploadFinished"
|
||||||
v-on:failed="onFailed"
|
v-on:failed="onFailed"
|
||||||
@@ -15,11 +14,8 @@
|
|||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { createUploaderComponent, useQuasar } from 'quasar';
|
import { createUploaderComponent, useQuasar } from 'quasar';
|
||||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
const $q=useQuasar();
|
const $q=useQuasar();
|
||||||
const authStore = useAuthStore();
|
|
||||||
const emit =defineEmits(['uploaded']);
|
const emit =defineEmits(['uploaded']);
|
||||||
/**
|
/**
|
||||||
* ファイルアップロードを拒否する時の処理
|
* ファイルアップロードを拒否する時の処理
|
||||||
@@ -30,7 +26,7 @@ import { ref } from 'vue';
|
|||||||
// https://quasar.dev/quasar-plugins/notify#Installation
|
// https://quasar.dev/quasar-plugins/notify#Installation
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: `Excelファイルを選択してください。`
|
message: `CSVおよびExcelファイルを選択してください。`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,28 +48,14 @@ import { ref } from 'vue';
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 例外発生時、responseからエラー情報を取得する
|
|
||||||
* @param xhr
|
|
||||||
*/
|
|
||||||
function getResponseError(xhr:XMLHttpRequest){
|
|
||||||
try{
|
|
||||||
const resp = JSON.parse(xhr.responseText);
|
|
||||||
return 'detail' in resp ? resp.detail:'';
|
|
||||||
}catch(err){
|
|
||||||
return xhr.responseText;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param info ファイルアップロード失敗時の処理
|
* @param info ファイルアップロード失敗時の処理
|
||||||
*/
|
*/
|
||||||
function onFailed({files,xhr}:{files: readonly any[],xhr:XMLHttpRequest}){
|
function onFailed({files,xhr}:{files: readonly any[],xhr:any}){
|
||||||
let msg ="ファイルアップロードが失敗しました。";
|
let msg ="ファイルアップロードが失敗しました。";
|
||||||
if(xhr && xhr.status){
|
if(xhr && xhr.status){
|
||||||
const detail = getResponseError(xhr);
|
msg=`${msg} (${xhr.status }:${xhr.statusText})`
|
||||||
msg=`${msg} (${xhr.status }:${detail})`
|
|
||||||
}
|
}
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type:"negative",
|
type:"negative",
|
||||||
@@ -85,12 +67,9 @@ import { ref } from 'vue';
|
|||||||
title: string;
|
title: string;
|
||||||
uploadUrl:string;
|
uploadUrl:string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = ref([{name:"Authorization",value:'Bearer ' + authStore.token}]);
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
title:"設計書から導入する(Excel)",
|
title:"設計書から導入する(csv or excel)",
|
||||||
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`
|
uploadUrl: `${process.env.KAB_BACKEND_URL}createappfromexcel`
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-btn-dropdown
|
|
||||||
class="customized-disabled-btn"
|
|
||||||
push
|
|
||||||
flat
|
|
||||||
no-caps
|
|
||||||
icon="share"
|
|
||||||
size="md"
|
|
||||||
:label="userStore.currentDomain.domainName"
|
|
||||||
:disable-dropdown="isUnclickable"
|
|
||||||
:dropdown-icon="isUnclickable ? 'none' : ''"
|
|
||||||
:disable="isUnclickable"
|
|
||||||
>
|
|
||||||
<q-list>
|
|
||||||
<q-item v-for="domain in domains" :key="domain.domainName"
|
|
||||||
clickable v-close-popup @click="onItemClick(domain)">
|
|
||||||
<q-item-section side>
|
|
||||||
<q-icon name="share" size="sm" color="orange" text-color="white"></q-icon>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{domain.domainName}}</q-item-label>
|
|
||||||
<q-item-label caption>{{domain.kintoneUrl}}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-btn-dropdown>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts" >
|
|
||||||
import { IDomainInfo } from 'src/types/ActionTypes';
|
|
||||||
import { useAuthStore,IUserState } from 'stores/useAuthStore';
|
|
||||||
import { ref, computed } from 'vue';
|
|
||||||
import { useRoute } from 'vue-router';
|
|
||||||
const userStore = useAuthStore();
|
|
||||||
const route = useRoute()
|
|
||||||
const domains = ref<IDomainInfo[]>([]);
|
|
||||||
(async ()=>{
|
|
||||||
domains.value = await userStore.getUserDomains();
|
|
||||||
})();
|
|
||||||
|
|
||||||
const isUnclickable = computed(()=>{
|
|
||||||
return route.path.startsWith('/FlowChart/') || domains.value === undefined || domains.value.length === 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
const onItemClick=(domain:IDomainInfo)=>{
|
|
||||||
console.log(domain);
|
|
||||||
userStore.setCurrentDomain(domain);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
.q-btn.disabled.customized-disabled-btn {
|
|
||||||
opacity: 1 !important;
|
|
||||||
cursor: default !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.q-btn.disabled.customized-disabled-btn * {
|
|
||||||
cursor: default !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="q-mx-md" style="max-width: 600px;">
|
|
||||||
<!-- <q-card> -->
|
|
||||||
<div class="q-mb-md">
|
|
||||||
<q-input ref="inputRef" v-if="!optionsRef|| optionsRef.length===0"
|
|
||||||
outlined dense debounce="200" @update:model-value="updateSharedText"
|
|
||||||
v-model="sharedText" :readonly="!canInputFlag" autogrow>
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-btn flat round padding="none" icon="cancel" @click="clearSharedText" color="grey-6" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
<q-select v-if="optionsRef && optionsRef.length>0"
|
|
||||||
:model-value="sharedText"
|
|
||||||
:options="optionsRef"
|
|
||||||
clearable
|
|
||||||
value-key="index"
|
|
||||||
outlined
|
|
||||||
dense
|
|
||||||
use-input
|
|
||||||
hide-selected
|
|
||||||
input-debounce="10"
|
|
||||||
fill-input
|
|
||||||
@input-value="setValue"
|
|
||||||
@clear="sharedText=null"
|
|
||||||
hide-dropdown-icon
|
|
||||||
:readonly="!canInputFlag"
|
|
||||||
>
|
|
||||||
</q-select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row q-gutter-sm">
|
|
||||||
<q-btn v-for="button in buttonsConfig" :key="button.type" :color="button.color" @mousedown.prevent
|
|
||||||
@click="openDialog(button)" size="sm">
|
|
||||||
{{ button.label }}
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<show-dialog v-model:visible="dialogVisible" :name="currentDialogName" @close="closeDialog" min-width="400px">
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-input dense debounce="200" v-model="filter" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
<!-- asdf -->
|
|
||||||
<component :is="currentComponent" @select="handleSelect" :filter="filter" :appId="appId" />
|
|
||||||
</show-dialog>
|
|
||||||
<!-- </q-card> -->
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { ref, inject, watchEffect, defineComponent,PropType } from 'vue';
|
|
||||||
import FieldAdd from './FieldAdd.vue';
|
|
||||||
import VariableAdd from './VariableAdd.vue';
|
|
||||||
// import FunctionAdd from './FunctionAdd.vue';
|
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
|
||||||
import { IButtonConfig } from 'src/types/ComponentTypes';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'DynamicItemInput',
|
|
||||||
components: {
|
|
||||||
FieldAdd,
|
|
||||||
VariableAdd,
|
|
||||||
// FunctionAdd,
|
|
||||||
ShowDialog
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
canInput: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
appId: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
selectedObject: {
|
|
||||||
default: {}
|
|
||||||
},
|
|
||||||
options:{
|
|
||||||
type:Array as PropType< string[]>
|
|
||||||
},
|
|
||||||
buttonsConfig: {
|
|
||||||
type: Array as PropType<IButtonConfig[]>,
|
|
||||||
default: () => [
|
|
||||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const filter = ref('');
|
|
||||||
const dialogVisible = ref(false);
|
|
||||||
const currentDialogName = ref('');
|
|
||||||
const selectedObjectRef = ref(props.selectedObject);
|
|
||||||
const currentComponent = ref('FieldAdd');
|
|
||||||
const sharedText = ref(props.selectedObject?.sharedText ?? '');
|
|
||||||
const inputRef = ref();
|
|
||||||
const canInputFlag = ref(props.canInput);
|
|
||||||
const editable = ref(false);
|
|
||||||
|
|
||||||
const openDialog = (button: IButtonConfig) => {
|
|
||||||
currentDialogName.value = button.label;
|
|
||||||
currentComponent.value = button.type;
|
|
||||||
dialogVisible.value = true;
|
|
||||||
editable.value = canInputFlag.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeDialog = () => {
|
|
||||||
dialogVisible.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSelect = (value:any) => {
|
|
||||||
|
|
||||||
if (value && value._t && (value._t as string).length > 0) {
|
|
||||||
canInputFlag.value = editable.value;
|
|
||||||
}
|
|
||||||
selectedObjectRef.value={ sharedText: value._t, ...value };
|
|
||||||
sharedText.value = `${value._t}`;
|
|
||||||
// emit('update:selectedObject', { sharedText: sharedText.value, ...value });
|
|
||||||
dialogVisible.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearSharedText = () => {
|
|
||||||
sharedText.value = '';
|
|
||||||
selectedObjectRef.value={};
|
|
||||||
canInputFlag.value = true;
|
|
||||||
// emit('update:selectedObject', {});
|
|
||||||
}
|
|
||||||
const updateSharedText = (value:string) => {
|
|
||||||
sharedText.value = value;
|
|
||||||
selectedObjectRef.value= { sharedText: value,objectType:'text' }
|
|
||||||
// emit('update:selectedObject', { ...props.selectedObject, sharedText: value,objectType:'text' });
|
|
||||||
}
|
|
||||||
const setValue=(value:string)=>{
|
|
||||||
sharedText.value = value;
|
|
||||||
if(selectedObjectRef.value.sharedText!==value){
|
|
||||||
selectedObjectRef.value= { sharedText: value,objectType:'text' }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const optionsRef=ref(props.options);
|
|
||||||
|
|
||||||
return {
|
|
||||||
filter,
|
|
||||||
dialogVisible,
|
|
||||||
currentDialogName,
|
|
||||||
currentComponent,
|
|
||||||
canInputFlag,
|
|
||||||
openDialog,
|
|
||||||
closeDialog,
|
|
||||||
handleSelect,
|
|
||||||
clearSharedText,
|
|
||||||
updateSharedText,
|
|
||||||
setValue,
|
|
||||||
sharedText,
|
|
||||||
inputRef,
|
|
||||||
optionsRef,
|
|
||||||
selectedObjectRef
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<template>
|
|
||||||
<field-list v-model="selected" type="single" :filter="filter" :appId="sourceApp ? sourceApp : appId"
|
|
||||||
:fields="sourceFields" @update:modelValue="handleSelect" />
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, inject, ref } from 'vue';
|
|
||||||
import FieldList from '../FieldList.vue';
|
|
||||||
export default {
|
|
||||||
name: 'FieldAdd',
|
|
||||||
components: {
|
|
||||||
FieldList,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
appId: Number,
|
|
||||||
filter: String
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const sourceFields = inject<Array<unknown>>('sourceFields')
|
|
||||||
const sourceApp = inject<number>('sourceApp')
|
|
||||||
const appId = computed(() => {
|
|
||||||
if (sourceFields || sourceApp) {
|
|
||||||
return sourceApp.value
|
|
||||||
} else {
|
|
||||||
return props.appId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
sourceFields,
|
|
||||||
sourceApp,
|
|
||||||
selected: ref([]),
|
|
||||||
handleSelect: (newSelection: any[]) => {
|
|
||||||
|
|
||||||
if (newSelection.length > 0) {
|
|
||||||
const v = newSelection[0]
|
|
||||||
emit('select', { _t: `field(${appId.value},${v.name})`, ...v }); // 假设您只需要选择的第一个字段的名称
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<template>
|
|
||||||
<variable-list v-model="selected" type="single" :vars="vars" :filter="filter" @update:modelValue="handleSelect" />
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import VariableList from '../VariableList.vue';
|
|
||||||
import { useFlowEditorStore } from 'src/stores/flowEditor';
|
|
||||||
import { IActionVariable } from 'src/types/ActionTypes';
|
|
||||||
export default {
|
|
||||||
name: 'VariableAdd',
|
|
||||||
components: {
|
|
||||||
VariableList,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
appId: Number,
|
|
||||||
filter: String
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const store = useFlowEditorStore();
|
|
||||||
let vars: IActionVariable[] = [];
|
|
||||||
console.log(store.currentFlow !== undefined && store.activeNode !== undefined);
|
|
||||||
|
|
||||||
if (store.currentFlow !== undefined && store.activeNode !== undefined) {
|
|
||||||
vars = store.currentFlow.getVarNames(store.activeNode);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
vars,
|
|
||||||
selected: ref([]),
|
|
||||||
handleSelect: (newSelection: any[]) => {
|
|
||||||
if (newSelection.length > 0) {
|
|
||||||
const v = newSelection[0];
|
|
||||||
let name = v.name
|
|
||||||
if (typeof name === 'object') {
|
|
||||||
name = name.name
|
|
||||||
}
|
|
||||||
emit('select', { _t: `var(${name})`, ...v }); // 假设您只需要选择的第一个字段的名称
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -19,7 +19,6 @@
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-separator
|
<q-separator
|
||||||
class="q-my-sm"
|
|
||||||
v-if="isSeparator"
|
v-if="isSeparator"
|
||||||
inset
|
inset
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="q-pa-md">
|
|
||||||
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" :selected="modelValue"
|
|
||||||
@update:selected="$emit('update:modelValue', $event)"
|
|
||||||
:filter="filter"
|
|
||||||
:columns="columns"
|
|
||||||
:rows="rows"
|
|
||||||
:pagination="pagination"
|
|
||||||
style="max-height: 55vh;"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { useAsyncState } from '@vueuse/core';
|
|
||||||
import { api } from 'boot/axios';
|
|
||||||
import { computed ,Prop,PropType,ref} from 'vue';
|
|
||||||
import {IField} from 'src/types/ComponentTypes';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'FieldList',
|
|
||||||
props: {
|
|
||||||
fields: Array as PropType<IField[]>,
|
|
||||||
name: String,
|
|
||||||
type: String,
|
|
||||||
appId: Number,
|
|
||||||
modelValue: Array,
|
|
||||||
filter: String
|
|
||||||
},
|
|
||||||
emits: [
|
|
||||||
'update:modelValue'
|
|
||||||
],
|
|
||||||
setup(props) {
|
|
||||||
// const rows = ref([]);
|
|
||||||
// const isLoaded = ref(false);
|
|
||||||
const columns = [
|
|
||||||
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: 'name', sortable: true },
|
|
||||||
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
|
|
||||||
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
|
||||||
]
|
|
||||||
|
|
||||||
const { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
|
|
||||||
if (props.fields && Object.keys(props.fields).length > 0) {
|
|
||||||
return props.fields.map(f => ({ name: f.label, ...f ,objectType: 'field'}));
|
|
||||||
} else {
|
|
||||||
return api.get('api/v1/appfields', {
|
|
||||||
params: {
|
|
||||||
app: props.appId
|
|
||||||
}
|
|
||||||
}).then(res => {
|
|
||||||
const fields = res.data.properties;
|
|
||||||
return Object.values(fields).map((f:any) => ({ name: f.label, objectType: 'field', ...f }));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [{ name: '', objectType: '', type: '', code: '', label: '' }])
|
|
||||||
return {
|
|
||||||
columns,
|
|
||||||
rows,
|
|
||||||
// selected: ref([]),
|
|
||||||
isLoaded,
|
|
||||||
pagination: ref({
|
|
||||||
rowsPerPage: 25,
|
|
||||||
sortBy: 'name',
|
|
||||||
descending: false,
|
|
||||||
page: 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,105 +1,45 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-px-md" style=" min-width: 50vw; max-width: 85vw;">
|
<div class="q-pa-md">
|
||||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
<q-table :title="name+'一覧'" row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
|
||||||
<q-spinner color="primary" size="3em" />
|
|
||||||
</div>
|
|
||||||
<q-table flat bordered v-else row-key="id" :selection="type" v-model:selected="selected" :columns="columns"
|
|
||||||
:rows="rows" :pagination="pageSetting" :filter="filter" style="max-height: 55vh;"/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { ref, onMounted, reactive, watchEffect } from 'vue'
|
import { ref,onMounted,reactive } from 'vue'
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'fieldSelect',
|
name: 'fieldSelect',
|
||||||
props: {
|
props: {
|
||||||
name: String,
|
name: String,
|
||||||
type: {
|
|
||||||
type: String,
|
type: String,
|
||||||
default: 'single'
|
appId:Number
|
||||||
},
|
|
||||||
appId: Number,
|
|
||||||
not_page: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
selectedFields:{
|
|
||||||
type:Array ,
|
|
||||||
default:()=>[]
|
|
||||||
},
|
|
||||||
fieldTypes:{
|
|
||||||
type:Array,
|
|
||||||
default:()=>[]
|
|
||||||
},
|
|
||||||
filter: String,
|
|
||||||
updateSelectFields: {
|
|
||||||
type: Function
|
|
||||||
},
|
|
||||||
blackListLabel: {
|
|
||||||
type:Array,
|
|
||||||
default:()=>[]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const isLoaded = ref(false);
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: row => row.name, sortable: true },
|
{ name: 'name', required: true,label: 'フィールド名',align: 'left',field: row=>row.name,sortable: true},
|
||||||
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
|
{ name: 'code', label: 'フィールドコード', align: 'left',field: 'code', sortable: true },
|
||||||
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
{ name: 'type', label: 'フィールドタイプ', align: 'left',field: 'type', sortable: true }
|
||||||
];
|
]
|
||||||
const pageSetting = ref({
|
const rows = reactive([])
|
||||||
sortBy: 'name',
|
onMounted( () => {
|
||||||
descending: false,
|
api.get('appfields', {
|
||||||
page: 1,
|
params:{
|
||||||
rowsPerPage: props.not_page ? 0 : 25
|
|
||||||
// rowsNumber: xx if getting data from a server
|
|
||||||
});
|
|
||||||
const rows = reactive([]);
|
|
||||||
const selected = ref((props.selectedFields && props.selectedFields.length>0)?props.selectedFields:[]);
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
const url = props.fieldTypes.includes('SPACER')?'api/v1/allfields':'api/v1/appfields';
|
|
||||||
const res = await api.get(url, {
|
|
||||||
params: {
|
|
||||||
app: props.appId
|
app: props.appId
|
||||||
}
|
}
|
||||||
|
}).then(res =>{
|
||||||
|
let fields = res.data.properties;
|
||||||
|
console.log(fields);
|
||||||
|
Object.keys(fields).forEach((key) =>
|
||||||
|
{
|
||||||
|
rows.push({name:fields[key].label,code:fields[key].code,type:fields[key].type});
|
||||||
|
}
|
||||||
|
)
|
||||||
});
|
});
|
||||||
let fields = Object.values(res.data.properties);
|
|
||||||
for (const index in fields) {
|
|
||||||
const fld = fields[index]
|
|
||||||
if(props.blackListLabel.length > 0){
|
|
||||||
if(!props.blackListLabel.find(blackListItem => blackListItem === fld.label)){
|
|
||||||
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
|
|
||||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
|
||||||
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
|
|
||||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
|
|
||||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
|
||||||
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
|
|
||||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isLoaded.value = true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
watchEffect(()=>{
|
|
||||||
if (selected.value && selected.value[0] && props.updateSelectFields) {
|
|
||||||
props.updateSelectFields(selected)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
rows,
|
rows,
|
||||||
selected,
|
selected: ref([]),
|
||||||
isLoaded,
|
|
||||||
pageSetting
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
13
frontend/src/components/PropertyList.vue
Normal file
13
frontend/src/components/PropertyList.vue
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="">
|
||||||
|
|
||||||
|
</style>
|
||||||
9
frontend/src/components/Rules.ts
Normal file
9
frontend/src/components/Rules.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export interface Rule{
|
||||||
|
id:number;
|
||||||
|
name:string;
|
||||||
|
condtion:CondtionTree
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CondtionTree{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,44 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- <div class="q-pa-md q-gutter-sm" > -->
|
<div class="q-pa-md q-gutter-sm">
|
||||||
<q-dialog :model-value="visible" persistent bordered >
|
<q-dialog :model-value="visible" persistent>
|
||||||
<q-card class="" style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
|
<q-card style="min-width: 350px">
|
||||||
<q-toolbar class="bg-grey-4">
|
<q-card-section>
|
||||||
<q-toolbar-title>{{ name }}</q-toolbar-title>
|
<div class="text-h6">{{ name }}選択</div>
|
||||||
<q-space></q-space>
|
</q-card-section>
|
||||||
<slot name="toolbar"></slot>
|
|
||||||
<q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" />
|
<q-card-section class="q-pt-none">
|
||||||
</q-toolbar>
|
|
||||||
<q-card-section class="q-mt-md" :style="sectionStyle">
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions v-if="!disableBtn" align="right" class="text-primary">
|
<q-card-actions align="right" class="text-primary">
|
||||||
<q-btn flat label="確定" :loading="okBtnLoading" :v-close-popup="okBtnAutoClose" @click="CloseDialogue('OK')" />
|
<q-btn flat label="確定" v-close-popup @click="CloseDialogue('OK')" />
|
||||||
<q-btn flat label="キャンセル" v-close-popup @click="CloseDialogue('Cancel')" />
|
<q-btn flat label="キャンセル" v-close-popup @click="CloseDialogue('Cancel')" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
<!-- </div> -->
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import {computed} from 'vue'
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ShowDialog',
|
name: 'showDialog',
|
||||||
props: {
|
props: {
|
||||||
name:String,
|
name:String,
|
||||||
visible: Boolean,
|
visible: Boolean,
|
||||||
width:String,
|
|
||||||
height:String,
|
|
||||||
minWidth:String,
|
|
||||||
minHeight:String,
|
|
||||||
okBtnLoading:Boolean,
|
|
||||||
okBtnAutoClose:{
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
disableBtn:{
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
'close'
|
'close'
|
||||||
@@ -49,20 +34,8 @@ export default {
|
|||||||
context.emit('close', val);
|
context.emit('close', val);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cardStyle = computed(() => ({
|
|
||||||
minWidth: props.minWidth,
|
|
||||||
width: props.width
|
|
||||||
}));
|
|
||||||
|
|
||||||
const sectionStyle = computed(() => ({
|
|
||||||
height: props.height,
|
|
||||||
minHeight: props.minHeight
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
CloseDialogue,
|
CloseDialogue
|
||||||
cardStyle,
|
|
||||||
sectionStyle
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
<template>
|
|
||||||
<q-table :rows="rows" :columns="columns" row-key="id" :filter="props.filter" :loading="loading"
|
|
||||||
:pagination="pagination" selection="single" v-model:selected="selected"></q-table>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
import { api } from 'boot/axios';
|
|
||||||
const props = defineProps<{filter:string}>()
|
|
||||||
const columns = [
|
|
||||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
|
||||||
{ name: 'firstName', label: '氏名', field: 'firstName', align: 'left', sortable: true },
|
|
||||||
{ name: 'lastName', label: '苗字', field: 'lastName', align: 'left', sortable: true },
|
|
||||||
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
|
||||||
];
|
|
||||||
|
|
||||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 10 });
|
|
||||||
const rows = ref([]);
|
|
||||||
const loading = ref(false);
|
|
||||||
const selected = ref([]);
|
|
||||||
defineExpose({
|
|
||||||
selected
|
|
||||||
})
|
|
||||||
const getUsers = async (filter = () => true) => {
|
|
||||||
loading.value = true;
|
|
||||||
const result = await api.get(`api/v1/users`);
|
|
||||||
rows.value = result.data.map((item) => {
|
|
||||||
return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active }
|
|
||||||
}).filter(filter);
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await getUsers();
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="q-pa-md">
|
|
||||||
<q-table flat bordered row-key="id" :selection="type" :selected="modelValue" :filter="filter"
|
|
||||||
@update:selected="$emit('update:modelValue', $event)" :columns="columns" :rows="rows" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { PropType, reactive } from 'vue';
|
|
||||||
import { IActionVariable } from '../types/ActionTypes';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'VariableList',
|
|
||||||
props: {
|
|
||||||
name: String,
|
|
||||||
type: String,
|
|
||||||
vars: {
|
|
||||||
type: Array as PropType<IActionVariable[]>,
|
|
||||||
reqired: true,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
modelValue: Array,
|
|
||||||
filter: String
|
|
||||||
},
|
|
||||||
emits: [
|
|
||||||
'update:modelValue'
|
|
||||||
],
|
|
||||||
setup(props) {
|
|
||||||
const variableName = (field) => {
|
|
||||||
const name = field.name;
|
|
||||||
return name.name;
|
|
||||||
}
|
|
||||||
const columns = [
|
|
||||||
{ name: 'actionName', label: 'アクション名', align: 'left', field: 'actionName', sortable: true },
|
|
||||||
{ name: 'displayName', label: '変数表示名', align: 'left', field: 'displayName', sortable: true },
|
|
||||||
{ name: 'name', label: '変数名', align: 'left', field: variableName, required: true, sortable: true }
|
|
||||||
];
|
|
||||||
|
|
||||||
const rows = props.vars.flatMap((v) => {
|
|
||||||
if (v.name.vars && v.name.vars.length > 0) {
|
|
||||||
return v.name.vars
|
|
||||||
.filter(o => o.vName && o.logicalOperator && o.field)
|
|
||||||
.map(o => ({
|
|
||||||
id: uuidv4(),
|
|
||||||
objectType: 'variable',
|
|
||||||
name: { name: `${v.name.name}.${o.vName}` },
|
|
||||||
actionName: v.name.actionName,
|
|
||||||
displayName: v.name.displayName
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
return [{ objectType: 'variable', ...v }];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
columns,
|
|
||||||
rows: reactive(rows)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="q-py-md">
|
|
||||||
<q-tree
|
|
||||||
no-connectors
|
|
||||||
selected-color="primary"
|
|
||||||
default-expand-all
|
|
||||||
:nodes="LeftDataBus.root"
|
|
||||||
v-model:selected="flowNames1"
|
|
||||||
node-key="label"
|
|
||||||
>
|
|
||||||
</q-tree>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
LeftDataBus,
|
|
||||||
setControlPanelE,
|
|
||||||
} from 'components/flowEditor/left/DataBus';
|
|
||||||
import { ref } from 'vue';
|
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
|
||||||
|
|
||||||
// 应该在page中用网络请求获取值并初始化组件
|
|
||||||
// 然后在page中执行setControlPane设置databus
|
|
||||||
const store = useFlowEditorStore();
|
|
||||||
const { flowNames1 } = storeToRefs(store);
|
|
||||||
setControlPanelE();
|
|
||||||
</script>
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
import { reactive } from 'vue'
|
|
||||||
|
|
||||||
export const LeftDataBus = reactive<LeftData>({})
|
|
||||||
|
|
||||||
const defaultData = {
|
|
||||||
root: [
|
|
||||||
{
|
|
||||||
label: 'レコードを追加画面',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
label: '追加画面表示した時',
|
|
||||||
header: 'rg',
|
|
||||||
value: '1-1',
|
|
||||||
group: 'g1',
|
|
||||||
children: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '保存をクリックした時',
|
|
||||||
header: 'rg',
|
|
||||||
value: '1-2',
|
|
||||||
group: 'g1',
|
|
||||||
children: []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '保存成功した時',
|
|
||||||
header: 'rg',
|
|
||||||
value: '1-3',
|
|
||||||
group: 'g1',
|
|
||||||
children: []
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'レコード編集画面',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'レコード詳細画面',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'レコード一覧画面',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
data: new Map([['g1', '1-1']])
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setControlPanel = (rootData: LeftData) => {
|
|
||||||
const { root: dr, data: dd } = defaultData
|
|
||||||
LeftDataBus.title = rootData.title
|
|
||||||
LeftDataBus.root = rootData.root ?? dr
|
|
||||||
LeftDataBus.data = rootData.data ?? dd
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setControlPanelE = () => {
|
|
||||||
const { root: dr, data: dd } = defaultData
|
|
||||||
// LeftDataBus.title = rootData.title
|
|
||||||
LeftDataBus.root = dr
|
|
||||||
LeftDataBus.data = dd
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LeftData {
|
|
||||||
title?: string
|
|
||||||
root?: ControlPanelData[]
|
|
||||||
data?: Map<string, string>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ControlPanelData {
|
|
||||||
label: string,
|
|
||||||
header?: string,
|
|
||||||
value?: string,
|
|
||||||
group?: string,
|
|
||||||
children?: ControlPanelData[]
|
|
||||||
}
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="row"
|
|
||||||
style="
|
|
||||||
border-radius: 2px;
|
|
||||||
box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 0px 1px inset,
|
|
||||||
rgba(0, 0, 0, 0.3) 0px 0px 0px 1px;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<q-icon
|
|
||||||
class="self-center q-ma-sm"
|
|
||||||
name="widgets"
|
|
||||||
color="grey-9"
|
|
||||||
style="font-size: 2em"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div class="col-7 self-center ellipsis">
|
|
||||||
{{ actName }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="self-center">
|
|
||||||
<q-btn
|
|
||||||
outline
|
|
||||||
dense
|
|
||||||
label="変 更"
|
|
||||||
padding="none sm"
|
|
||||||
color="primary"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['actName'],
|
|
||||||
setup(props) {
|
|
||||||
const actName = computed(() => props.actName);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="row app-box">
|
|
||||||
<q-icon
|
|
||||||
class="self-center q-ma-sm"
|
|
||||||
name="widgets"
|
|
||||||
color="grey-9"
|
|
||||||
style="font-size: 2em"
|
|
||||||
/>
|
|
||||||
<div class="col-7 self-center ellipsis">
|
|
||||||
<a :href="!store.appInfo?'':`${authStore.currentDomain.kintoneUrl}/k/${store.appInfo?.appId}`" target="_blank" title="Kiontoneへ">
|
|
||||||
{{ store.appInfo?.name }}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="self-center">
|
|
||||||
<q-btn
|
|
||||||
outline
|
|
||||||
dense
|
|
||||||
label="変 更"
|
|
||||||
padding="none sm"
|
|
||||||
color="primary"
|
|
||||||
@click="showAppDialog"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ShowDialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeDg" min-width="50vw" min-height="50vh" >
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelectBox>
|
|
||||||
</ShowDialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent,ref } from 'vue';
|
|
||||||
import {AppInfo} from '../../types/ActionTypes'
|
|
||||||
import ShowDialog from '../../components/ShowDialog.vue';
|
|
||||||
import AppSelectBox from '../../components/AppSelectBox.vue';
|
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
|
||||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'AppSelector',
|
|
||||||
emits:[
|
|
||||||
"appSelected"
|
|
||||||
],
|
|
||||||
components:{
|
|
||||||
AppSelectBox,
|
|
||||||
ShowDialog
|
|
||||||
},
|
|
||||||
setup(props, context) {
|
|
||||||
|
|
||||||
const store = useFlowEditorStore();
|
|
||||||
const authStore=useAuthStore();
|
|
||||||
const appDg = ref();
|
|
||||||
const showSelectApp=ref(false);
|
|
||||||
|
|
||||||
const closeDg=(val :any)=>{
|
|
||||||
showSelectApp.value=false;
|
|
||||||
console.log("Dialog closed->",val);
|
|
||||||
if (val == 'OK') {
|
|
||||||
const data = appDg.value.selected[0];
|
|
||||||
console.log(data);
|
|
||||||
const appInfo={
|
|
||||||
appId:data.id ,
|
|
||||||
name:data.name
|
|
||||||
};
|
|
||||||
store.setApp(appInfo);
|
|
||||||
store.loadFlow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const showAppDialog=()=>{
|
|
||||||
showSelectApp.value=true;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
store,
|
|
||||||
authStore,
|
|
||||||
showSelectApp,
|
|
||||||
showAppDialog,
|
|
||||||
closeDg,
|
|
||||||
appDg,
|
|
||||||
filter:ref('')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
.app-box{
|
|
||||||
border-radius: 2px;
|
|
||||||
box-shadow: rgba(255, 255, 255, 0.1) 0px 0px 0px 1px inset,rgba(0, 0, 0, 0.3) 0px 0px 0px 1px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
<template>
|
|
||||||
<!-- <div class="q-pa-md q-gutter-sm"> -->
|
|
||||||
<q-tree :nodes="store.eventTree.screens" node-key="eventId" children-key="events" no-connectors
|
|
||||||
v-model:expanded="store.expandedScreen" :dense="true" :ref="tree">
|
|
||||||
<template v-slot:header-EVENT="prop">
|
|
||||||
<div :ref="prop.node.eventId" class="row col items-center no-wrap event-node" @click="onSelected(prop.node)">
|
|
||||||
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
|
|
||||||
class="q-mr-sm">
|
|
||||||
</q-icon>
|
|
||||||
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div>
|
|
||||||
<q-space></q-space>
|
|
||||||
<!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> -->
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-slot:header-CHANGE="prop">
|
|
||||||
<div class="row col items-start no-wrap event-node">
|
|
||||||
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div>
|
|
||||||
<q-space></q-space>
|
|
||||||
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm"
|
|
||||||
@click="addChangeEvent(prop.node)"></q-icon>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-slot:header-DELETABLE="prop">
|
|
||||||
<div class="row col items-start event-node" @click="onSelected(prop.node)">
|
|
||||||
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px" class="q-mr-sm" />
|
|
||||||
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div>
|
|
||||||
<q-space></q-space>
|
|
||||||
<q-icon name="delete_forever" color="negative" size="16px" @click="deleteEvent(prop.node)"></q-icon>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</q-tree>
|
|
||||||
<show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg">
|
|
||||||
<field-select ref="appDg" name="フィールド" type="single" :fieldTypes="fieldTypes" :appId="store.appInfo?.appId"></field-select>
|
|
||||||
</show-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { QTree, useQuasar } from 'quasar';
|
|
||||||
import { ActionFlow, RootAction } from 'src/types/ActionTypes';
|
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
|
||||||
import { defineComponent, ref, watchEffect } from 'vue';
|
|
||||||
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode, kintoneEvent } from '../../types/KintoneEvents';
|
|
||||||
import FieldSelect from '../FieldSelect.vue';
|
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'EventTree',
|
|
||||||
components: {
|
|
||||||
ShowDialog,
|
|
||||||
FieldSelect,
|
|
||||||
},
|
|
||||||
setup(props, context) {
|
|
||||||
const $q = useQuasar();
|
|
||||||
const appDg = ref();
|
|
||||||
const store = useFlowEditorStore();
|
|
||||||
const showDialog = ref(false);
|
|
||||||
const tree = ref<QTree>();
|
|
||||||
const fieldTypes=[
|
|
||||||
'RADIO_BUTTON',
|
|
||||||
'DROP_DOWN',
|
|
||||||
'CHECK_BOX',
|
|
||||||
'MULTI_SELECT',
|
|
||||||
'USER_SELECT',
|
|
||||||
'GROUP_SELECT',
|
|
||||||
'ORGANIZATION_SELECT',
|
|
||||||
'DATE',
|
|
||||||
'DATETIME',
|
|
||||||
'TIME',
|
|
||||||
'SINGLE_LINE_TEXT',
|
|
||||||
'NUMBER'];
|
|
||||||
// const eventTree=ref(kintoneEvents);
|
|
||||||
// const selectedFlow = store.currentFlow;
|
|
||||||
|
|
||||||
// const expanded=ref();
|
|
||||||
const selectedEvent = ref<IKintoneEvent | undefined>(store.selectedEvent);
|
|
||||||
const selectedChangeEvent = ref<IKintoneEventGroup | undefined>(undefined);
|
|
||||||
const isFieldChange = (node: IKintoneEventNode) => {
|
|
||||||
return node.header == 'EVENT' && node.eventId.indexOf(".change.") > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSelectedClass = (node: IKintoneEventNode) => {
|
|
||||||
return store.selectedEvent && node.eventId === store.selectedEvent.eventId ? 'selected-node' : '';
|
|
||||||
};
|
|
||||||
|
|
||||||
//フィールド値変更イベント追加
|
|
||||||
const closeDg = (val: string) => {
|
|
||||||
if (val == 'OK') {
|
|
||||||
if (!selectedChangeEvent.value) { return; }
|
|
||||||
const field = appDg.value.selected[0];
|
|
||||||
const eventid = `${selectedChangeEvent.value.eventId}.${field.code}`;
|
|
||||||
if (store.eventTree.findEventById(eventid)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedChangeEvent.value?.events.push(new kintoneEvent(
|
|
||||||
field.name,
|
|
||||||
eventid,
|
|
||||||
selectedChangeEvent.value.eventId,
|
|
||||||
'DELETABLE'
|
|
||||||
));
|
|
||||||
tree.value?.expanded?.push(selectedChangeEvent.value.eventId);
|
|
||||||
tree.value?.expandAll();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const addChangeEvent = (node: IKintoneEventGroup) => {
|
|
||||||
if (store.appInfo === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedChangeEvent.value = node;
|
|
||||||
showDialog.value = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteEvent = (node: IKintoneEvent) => {
|
|
||||||
if (!node.eventId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
store.deleteEvent(node);
|
|
||||||
store.selectFlow(undefined)
|
|
||||||
|
|
||||||
$q.notify({
|
|
||||||
type: 'positive',
|
|
||||||
caption: "通知",
|
|
||||||
message: `イベント ${node.label} 削除`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const onSelected = (node: IKintoneEvent) => {
|
|
||||||
if (!node.eventId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedEvent.value = node;
|
|
||||||
if (store.appInfo === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const screen = store.eventTree.findEventById(node.parentId);
|
|
||||||
|
|
||||||
let flow = store.findFlowByEventId(node.eventId);
|
|
||||||
let screenName = screen !== null ? screen.label : '';
|
|
||||||
let nodeLabel = node.label;
|
|
||||||
// if(isFieldChange(node)){
|
|
||||||
// screenName=nodeLabel;
|
|
||||||
// nodeLabel=`${node.label}の値を変更したとき`;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (flow !== undefined && flow !== null) {
|
|
||||||
store.selectFlow(flow);
|
|
||||||
} else {
|
|
||||||
const root = new RootAction(node.eventId, screenName, nodeLabel)
|
|
||||||
const flow = new ActionFlow(root);
|
|
||||||
store.flows?.push(flow);
|
|
||||||
store.selectFlow(flow);
|
|
||||||
selectedEvent.value.flowData = flow;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
watchEffect(()=>{
|
|
||||||
store.setCurrentEvent(selectedEvent.value);
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
// eventTree,
|
|
||||||
// expanded,
|
|
||||||
appDg,
|
|
||||||
tree,
|
|
||||||
showDialog,
|
|
||||||
isFieldChange,
|
|
||||||
getSelectedClass,
|
|
||||||
onSelected,
|
|
||||||
selectedEvent,
|
|
||||||
addChangeEvent,
|
|
||||||
deleteEvent,
|
|
||||||
closeDg,
|
|
||||||
store,
|
|
||||||
fieldTypes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
.nowrap {
|
|
||||||
flex-wrap: nowarp;
|
|
||||||
text-wrap: nowarp;
|
|
||||||
}
|
|
||||||
|
|
||||||
.event-node {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-node {
|
|
||||||
color: $primary;
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
.event-node:hover {
|
|
||||||
background-color: $light-blue-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-btn {
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
0
frontend/src/components/main/ActionNode.vue
Normal file
0
frontend/src/components/main/ActionNode.vue
Normal file
@@ -1,276 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="row justify-center no-wrap" >
|
|
||||||
<div class="row">
|
|
||||||
<q-card class="action-node" :class="nodeStyle" :square="false" @click="onNodeClick" >
|
|
||||||
<q-toolbar class="col" >
|
|
||||||
<div class="text-subtitle2">{{ node.subTitle }}</div>
|
|
||||||
<q-space></q-space>
|
|
||||||
<q-btn flat round dense icon="more_horiz" size="sm" >
|
|
||||||
<q-menu auto-close anchor="top right">
|
|
||||||
<q-list>
|
|
||||||
<q-item clickable v-if="isRoot" @click="copyFlow">
|
|
||||||
<q-item-section avatar><q-icon name="content_copy" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >コピーする</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable v-if="!isRoot" @click="onEditNode">
|
|
||||||
<q-item-section avatar><q-icon name="edit" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >編集する</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable v-if="!isRoot" @click="onDeleteNode">
|
|
||||||
<q-item-section avatar><q-icon name="delete" ></q-icon></q-item-section>
|
|
||||||
<q-item-section>削除する</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable @click="onDeleteAllNode">
|
|
||||||
<q-item-section avatar><q-icon name="delete_sweep" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >以下すべて削除する</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-menu>
|
|
||||||
</q-btn>
|
|
||||||
</q-toolbar>
|
|
||||||
<q-separator />
|
|
||||||
<q-card-section class="action-title">
|
|
||||||
<div class="row">
|
|
||||||
<span class="text-h7">{{ node.title }}</span>
|
|
||||||
<q-space></q-space>
|
|
||||||
<q-chip color="info" text-color="white" size="0.70rem" v-if="varName(node)" clickable>{{ varName(node) }}</q-chip>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
<template v-if="hasBranch">
|
|
||||||
<q-separator />
|
|
||||||
<q-card-actions align="around">
|
|
||||||
<q-btn flat v-for="(point, index) in node.outputPoints" :key="index">
|
|
||||||
{{ point }}
|
|
||||||
</q-btn>
|
|
||||||
</q-card-actions>
|
|
||||||
</template>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template v-if="hasBranch">
|
|
||||||
<node-line :action-node="node" @addNode="addNode" :left-columns="leftColumns" :right-columns="rightColumns"></node-line>
|
|
||||||
<div class="row justify-center no-wrap" >
|
|
||||||
<div v-for="(point, index) in node.outputPoints" :key="index" class="column" style="min-width: 300px;">
|
|
||||||
<div class="justify-center" >
|
|
||||||
<node-item v-if="nextNode(point)!==undefined" :key="nextNode(point).id" :isSelected="nextNode(point) === store.activeNode"
|
|
||||||
:actionNode="nextNode(point)" @addNode="addNodeFromItem" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
|
|
||||||
@deleteNode="onDeleteNodeFromItem" @deleteAllNextNodes="onDeleteAllNextNodes" ></node-item>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-if="!hasBranch">
|
|
||||||
<div class="row justify-center no-wrap" >
|
|
||||||
<node-line :action-node="node" @addNode="addNode" ></node-line>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<node-item v-if="nextNode('')!==undefined" :key="nextNode('').id" :isSelected="nextNode('') === store.activeNode"
|
|
||||||
:actionNode="nextNode('')" @addNode="addNodeFromItem" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
|
|
||||||
@deleteNode="onDeleteNodeFromItem" @deleteAllNextNodes="onDeleteAllNextNodes" ></node-item>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, computed, ref } from 'vue';
|
|
||||||
import { IActionNode, IActionProperty } from '../../types/ActionTypes';
|
|
||||||
import NodeLine, { Direction } from '../main/NodeLine.vue';
|
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'NodeItem',
|
|
||||||
components: {
|
|
||||||
NodeLine
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
actionNode: {
|
|
||||||
type: Object as () => IActionNode,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
isSelected: {
|
|
||||||
type: Boolean
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emits: [
|
|
||||||
'addNode',
|
|
||||||
"nodeSelected",
|
|
||||||
"nodeEdit",
|
|
||||||
"deleteNode",
|
|
||||||
"deleteAllNextNodes",
|
|
||||||
"copyFlow"
|
|
||||||
],
|
|
||||||
setup(props, context) {
|
|
||||||
const store = useFlowEditorStore();
|
|
||||||
const hasBranch = computed(() => props.actionNode.outputPoints.length > 0);
|
|
||||||
const nodeStyle = computed(() => {
|
|
||||||
return {
|
|
||||||
'root-node': props.actionNode.isRoot,
|
|
||||||
'text-white': props.actionNode.isRoot,
|
|
||||||
'selected': props.isSelected && !props.actionNode.isRoot
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const nextNode=(point:string)=>{
|
|
||||||
const nextId= props.actionNode.nextNodeIds.get(point);
|
|
||||||
if(!nextId) return undefined;
|
|
||||||
return store.currentFlow?.findNodeById(nextId);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* アクションノード追加イベントを
|
|
||||||
* @param point 入力ポイント
|
|
||||||
*/
|
|
||||||
const addNode = (point: string) => {
|
|
||||||
context.emit('addNode', props.actionNode, point);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* アクションノード追加イベントを
|
|
||||||
* @param point 入力ポイント
|
|
||||||
*/
|
|
||||||
const addNodeFromItem = (node:IActionNode,point: string) => {
|
|
||||||
context.emit('addNode', node, point);
|
|
||||||
}
|
|
||||||
|
|
||||||
const leftColumns=computed(()=>{
|
|
||||||
if(!props.actionNode.outputPoints || props.actionNode.outputPoints.length<2){
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
const leftNode = nextNode(props.actionNode.outputPoints[0]);
|
|
||||||
if(leftNode){
|
|
||||||
return store.currentFlow?.getColumns(leftNode);
|
|
||||||
}else{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const rightColumns=computed(()=>{
|
|
||||||
if(!props.actionNode.outputPoints || props.actionNode.outputPoints.length<2){
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
const rightNode = nextNode(props.actionNode.outputPoints[1]);
|
|
||||||
if(rightNode){
|
|
||||||
return store.currentFlow?.getColumns(rightNode);
|
|
||||||
}else{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ノード選択状態
|
|
||||||
*/
|
|
||||||
const onNodeClick = () => {
|
|
||||||
context.emit('nodeSelected', props.actionNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const onNodeSelected = (node: IActionNode) => {
|
|
||||||
context.emit('nodeSelected', node);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onEditNode=()=>{
|
|
||||||
context.emit('nodeEdit', props.actionNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onNodeEdit=(node:IActionNode)=>{
|
|
||||||
context.emit('nodeEdit', node);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ノードを削除する
|
|
||||||
*/
|
|
||||||
const onDeleteNode=()=>{
|
|
||||||
context.emit('deleteNode', props.actionNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ノードを削除する
|
|
||||||
*/
|
|
||||||
const onDeleteNodeFromItem=(node:IActionNode)=>{
|
|
||||||
context.emit('deleteNode', node);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* ノードの以下すべて削除する
|
|
||||||
*/
|
|
||||||
const onDeleteAllNode=()=>{
|
|
||||||
context.emit('deleteAllNextNodes', props.actionNode);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ノードの以下すべて削除する
|
|
||||||
*/
|
|
||||||
const onDeleteAllNextNodes=(node:IActionNode)=>{
|
|
||||||
context.emit('deleteAllNextNodes', node);
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* 変数名取得
|
|
||||||
*/
|
|
||||||
const varName =(node:IActionNode)=>{
|
|
||||||
const prop = node.actionProps.find((prop) => prop.props.name === "verName");
|
|
||||||
return prop?.props.modelValue.name;
|
|
||||||
};
|
|
||||||
const copyFlow=()=>{
|
|
||||||
context.emit('copyFlow', props.actionNode);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
store,
|
|
||||||
node: props.actionNode,
|
|
||||||
nextNode,
|
|
||||||
isRoot: props.actionNode.isRoot,
|
|
||||||
hasBranch,
|
|
||||||
nodeStyle,
|
|
||||||
// getMode,
|
|
||||||
addNode,
|
|
||||||
addNodeFromItem,
|
|
||||||
onNodeClick,
|
|
||||||
onNodeSelected,
|
|
||||||
onEditNode,
|
|
||||||
onNodeEdit,
|
|
||||||
onDeleteNode,
|
|
||||||
onDeleteNodeFromItem,
|
|
||||||
onDeleteAllNode,
|
|
||||||
onDeleteAllNextNodes,
|
|
||||||
copyFlow,
|
|
||||||
varName,
|
|
||||||
leftColumns,
|
|
||||||
rightColumns
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
.action-node {
|
|
||||||
min-width: 280px !important;
|
|
||||||
}
|
|
||||||
.action-title{
|
|
||||||
max-width: 280px !important;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line {
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line:after {
|
|
||||||
content: '';
|
|
||||||
background-color: $blue-7;
|
|
||||||
display: block;
|
|
||||||
width: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-icon {
|
|
||||||
font-size: 2em;
|
|
||||||
color: $blue-7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.root-node {
|
|
||||||
background-color: $blue-7;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-node:not(.root-node):hover{
|
|
||||||
background-color: $light-blue-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected{
|
|
||||||
background-color: $yellow-1;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="row justify-center">
|
|
||||||
<svg class="node-line" style="width:100%" :viewBox="viewBox()">
|
|
||||||
<template v-if="!node.outputPoints || node.outputPoints.length===0" >
|
|
||||||
<polyline :points="points(getMode('')).linePoints" class="line" ></polyline>
|
|
||||||
<text class="add-icon"
|
|
||||||
@click="addNode(node,'')"
|
|
||||||
:x="points(getMode('')).iconPoint.x"
|
|
||||||
:y="points(getMode('')).iconPoint.y"
|
|
||||||
font-family="Arial" font-size="25"
|
|
||||||
text-anchor="middle" dy=".3em" style="cursor: pointer;" >
|
|
||||||
⊕
|
|
||||||
</text>
|
|
||||||
</template>
|
|
||||||
<template v-for="(point, index) in node.outputPoints" :key="index" >
|
|
||||||
<polyline :points="points(getMode(point)).linePoints" class="line" ></polyline>
|
|
||||||
<text class="add-icon"
|
|
||||||
@click="addNode(node,point)"
|
|
||||||
:x="points(getMode(point)).iconPoint.x"
|
|
||||||
:y="points(getMode(point)).iconPoint.y"
|
|
||||||
font-family="Arial" font-size="25"
|
|
||||||
text-anchor="middle" dy=".3em" style="cursor: pointer;" >
|
|
||||||
⊕
|
|
||||||
</text>
|
|
||||||
</template>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { ref, defineComponent, computed, PropType } from 'vue';
|
|
||||||
import { IActionNode, ActionNode, ActionFlow, RootAction } from '../../types/ActionTypes';
|
|
||||||
export enum Direction {
|
|
||||||
Default = "None",
|
|
||||||
Left = "LEFT",
|
|
||||||
Right = "RIGHT",
|
|
||||||
LeftNotNext = "LEFTNOTNEXT",
|
|
||||||
RightNotNext = "RIGHTNOTNEXT",
|
|
||||||
}
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'NodeLine',
|
|
||||||
props: {
|
|
||||||
actionNode: {
|
|
||||||
type: Object as PropType<IActionNode>,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
leftColumns:{
|
|
||||||
type:Number,
|
|
||||||
required:false
|
|
||||||
},
|
|
||||||
rightColumns:{
|
|
||||||
type:Number,
|
|
||||||
required:false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emits: ['addNode'],
|
|
||||||
setup(props,context) {
|
|
||||||
const hasBranch = computed(() => props.actionNode.outputPoints.length > 0);
|
|
||||||
const getMode = (point: string):Direction => {
|
|
||||||
if (point === '' || props.actionNode.outputPoints.length === 0) {
|
|
||||||
return Direction.Default;
|
|
||||||
}
|
|
||||||
if (point === props.actionNode.outputPoints[0]) {
|
|
||||||
if (props.actionNode.nextNodeIds.get(point)) {
|
|
||||||
return Direction.Left;
|
|
||||||
} else {
|
|
||||||
return Direction.LeftNotNext;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (props.actionNode.nextNodeIds.get(point)) {
|
|
||||||
return Direction.Right;
|
|
||||||
} else {
|
|
||||||
return Direction.RightNotNext;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const points = (mode:Direction) => {
|
|
||||||
let startX ,endX;
|
|
||||||
const leftColumn=props.leftColumns?props.leftColumns:1;
|
|
||||||
const rightColumn=props.rightColumns?props.rightColumns:1;
|
|
||||||
|
|
||||||
switch (mode) {
|
|
||||||
case Direction.Left:
|
|
||||||
startX = leftColumn*300/2.0;
|
|
||||||
endX = ((leftColumn+rightColumn)/2.0 - 0.25)*300;
|
|
||||||
return {
|
|
||||||
linePoints: `${startX}, 60, ${startX}, 40, ${endX}, 40, ${endX}, 0`,
|
|
||||||
iconPoint: { x: endX, y: 20 }
|
|
||||||
};
|
|
||||||
case Direction.Right:
|
|
||||||
startX = ((leftColumn+rightColumn)/2.0 + 0.25)*300;
|
|
||||||
endX = (leftColumn+(rightColumn/2.0))*300;
|
|
||||||
return {
|
|
||||||
linePoints: `${startX}, 0, ${startX}, 40, ${endX}, 40, ${endX}, 60`,
|
|
||||||
iconPoint: { x: startX, y: 20 }
|
|
||||||
};
|
|
||||||
case Direction.LeftNotNext:
|
|
||||||
startX = ((leftColumn+rightColumn)/2.0 - 0.25)*300;
|
|
||||||
return {
|
|
||||||
linePoints: `${startX}, 0, ${startX}, 40`,
|
|
||||||
iconPoint: { x: startX, y: 20 }
|
|
||||||
};
|
|
||||||
case Direction.RightNotNext:
|
|
||||||
startX = ((leftColumn+rightColumn)/2.0 + 0.25)*300;
|
|
||||||
return {
|
|
||||||
linePoints: `${startX}, 0, ${startX}, 40`,
|
|
||||||
iconPoint: { x: startX, y: 20 }
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
linePoints: '150, 0, 150, 60',
|
|
||||||
iconPoint: { x: 150, y: 30 }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const addNode=(prveNode:IActionNode,point:string)=>{
|
|
||||||
context.emit('addNode',point);
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewBox=()=>{
|
|
||||||
let columns=0;
|
|
||||||
if(props.leftColumns!==undefined) columns+=props.leftColumns;
|
|
||||||
if(props.rightColumns!==undefined) columns+=props.rightColumns;
|
|
||||||
if(columns===0) columns=1;
|
|
||||||
const width= columns*300;
|
|
||||||
return `0 0 ${width} 60`;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
node: props.actionNode,
|
|
||||||
getMode,
|
|
||||||
hasBranch,
|
|
||||||
points,
|
|
||||||
addNode,
|
|
||||||
viewBox
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
.node-line {
|
|
||||||
height: 60px;
|
|
||||||
width: 240px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line {
|
|
||||||
stroke: $blue-7;
|
|
||||||
fill: none;
|
|
||||||
stroke-width: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-icon {
|
|
||||||
stroke: $blue-8;
|
|
||||||
fill: $blue-8;
|
|
||||||
font-family: Arial;
|
|
||||||
pointer-events: all;
|
|
||||||
font-size: 2.0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-icon:hover{
|
|
||||||
stroke: $blue-8;
|
|
||||||
fill:$blue-8;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 2.4em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -32,3 +32,4 @@ export interface AppInfo {
|
|||||||
creator?:User;
|
creator?:User;
|
||||||
modifier?:User;
|
modifier?:User;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div v-for="(item, index) in componentData" :key="index">
|
|
||||||
<component :is="item.component" v-bind="item.props" :connectProps="connectProps" v-model="item.props.modelValue"></component>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent,computed } from 'vue';
|
|
||||||
import InputText from '../right/InputText.vue';
|
|
||||||
import SelectBox from '../right/SelectBox.vue';
|
|
||||||
import DatePicker from '../right/DatePicker.vue';
|
|
||||||
import FieldInput from '../right/FieldInput.vue';
|
|
||||||
import EventSetter from '../right/EventSetter.vue';
|
|
||||||
import { IActionProperty, IProp } from 'src/types/ActionTypes';
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'ActionProperty',
|
|
||||||
components: {
|
|
||||||
InputText,
|
|
||||||
SelectBox,
|
|
||||||
DatePicker,
|
|
||||||
FieldInput,
|
|
||||||
EventSetter
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
jsonData: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
jsonValue:{
|
|
||||||
type: Object,
|
|
||||||
required: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props){
|
|
||||||
const componentData=computed<Array<IActionProperty>>(()=>{
|
|
||||||
return props.jsonData.elements.map((element: any) => {
|
|
||||||
if(props.jsonValue != undefined )
|
|
||||||
{
|
|
||||||
if(props.jsonValue.hasOwnProperty(element.props.name))
|
|
||||||
{
|
|
||||||
element.props.modelValue = props.jsonValue[element.props.name];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
element.props.modelValue = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
component: element.component,
|
|
||||||
props: element.props,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
const connectProps=(props:IProp)=>{
|
|
||||||
const connProps:any={};
|
|
||||||
if(props && "connectProps" in props && props.connectProps!=undefined){
|
|
||||||
for(let connProp of props.connectProps){
|
|
||||||
let targetProp = componentData.value.find((prop)=>prop.props.name===connProp.propName);
|
|
||||||
if(targetProp){
|
|
||||||
connProps[connProp.key]=targetProp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return connProps;
|
|
||||||
}
|
|
||||||
|
|
||||||
return{
|
|
||||||
componentData,
|
|
||||||
connectProps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="q-my-md" v-bind="$attrs">
|
|
||||||
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
|
|
||||||
:rules="rulesExp" lazy-rules="ondemand" @clear="clear" ref="fieldRef">
|
|
||||||
<template v-slot:control>
|
|
||||||
{{ isSelected ? selectedField.app?.name : "(未選択)" }}
|
|
||||||
</template>
|
|
||||||
<template v-slot:hint v-if="!isSelected">
|
|
||||||
{{ placeholder }}
|
|
||||||
</template>
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
|
||||||
</template>
|
|
||||||
</q-field>
|
|
||||||
<div v-if="selectedField.fields && selectedField.fields.length > 0">
|
|
||||||
<q-list bordered>
|
|
||||||
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator v-slot="{ item, index }">
|
|
||||||
<q-item :key="index" dense clickable>
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>
|
|
||||||
{{ item.label }}
|
|
||||||
</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
<q-item-section side>
|
|
||||||
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-virtual-scroll>
|
|
||||||
</q-list>
|
|
||||||
</div>
|
|
||||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeAFBox">
|
|
||||||
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" ref="afBox"
|
|
||||||
:fieldTypes="fieldTypes" />
|
|
||||||
</show-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, ref, watchEffect } from 'vue';
|
|
||||||
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
|
||||||
|
|
||||||
export interface IApp {
|
|
||||||
id: string,
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
export interface IField {
|
|
||||||
name: string,
|
|
||||||
code: string,
|
|
||||||
type: string,
|
|
||||||
label?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAppFields {
|
|
||||||
app?: IApp,
|
|
||||||
fields: IField[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
inheritAttrs: false,
|
|
||||||
name: 'AppFieldSelect2',
|
|
||||||
components: {
|
|
||||||
ShowDialog,
|
|
||||||
AppFieldSelectBox
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
displayName: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: Object,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
selectType: {
|
|
||||||
type: String,
|
|
||||||
default: 'single'
|
|
||||||
},
|
|
||||||
fieldTypes: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const show = ref(false);
|
|
||||||
const afBox = ref();
|
|
||||||
const fieldRef = ref();
|
|
||||||
const selectedField = ref<IAppFields>({
|
|
||||||
app: undefined,
|
|
||||||
fields: []
|
|
||||||
});
|
|
||||||
if (props.modelValue && 'app' in props.modelValue && 'fields' in props.modelValue) {
|
|
||||||
selectedField.value = props.modelValue as IAppFields;
|
|
||||||
}
|
|
||||||
const store = useFlowEditorStore();
|
|
||||||
|
|
||||||
const clear = () => {
|
|
||||||
selectedField.value = {
|
|
||||||
fields: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeField = (index: number) => {
|
|
||||||
selectedField.value.fields.splice(index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeAFBox = (val: string) => {
|
|
||||||
if (val == 'OK') {
|
|
||||||
console.log(afBox.value);
|
|
||||||
selectedField.value = afBox.value.selField;
|
|
||||||
fieldRef.value.validate();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isSelected = computed(() => {
|
|
||||||
return !!selectedField.value.app
|
|
||||||
});
|
|
||||||
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
|
||||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
|
||||||
const requiredExp = props.required ? [((val: any) => (val && val.app && val.fields && val.fields.length > 0) || errmsg)] : [];
|
|
||||||
const rulesExp = [...requiredExp, ...customExp];
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
emit('update:modelValue', selectedField.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
store,
|
|
||||||
afBox,
|
|
||||||
show,
|
|
||||||
showDg: () => { show.value = true },
|
|
||||||
selectedField,
|
|
||||||
clear,
|
|
||||||
removeField,
|
|
||||||
closeAFBox,
|
|
||||||
isSelected,
|
|
||||||
rulesExp,
|
|
||||||
fieldRef
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-bind="$attrs">
|
|
||||||
<q-field :label="displayName" labelColor="primary" stack-label
|
|
||||||
:rules="rulesExp"
|
|
||||||
lazy-rules="ondemand"
|
|
||||||
v-model="selectedApp"
|
|
||||||
ref="fieldRef">
|
|
||||||
<template v-slot:control>
|
|
||||||
<q-card flat class="full-width">
|
|
||||||
<q-card-actions vertical>
|
|
||||||
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">アプリ選択</q-btn>
|
|
||||||
</q-card-actions>
|
|
||||||
<q-card-section class="text-caption">
|
|
||||||
<div v-if="selectedApp.app.name">
|
|
||||||
{{ selectedApp.app.name }}
|
|
||||||
</div>
|
|
||||||
<div v-else>{{ placeholder }}</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
</q-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ShowDialog v-model:visible="dgIsShow" name="アプリ選択" @close="closeDg" min-width="50vw" min-height="50vh">
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelectBox>
|
|
||||||
</ShowDialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, reactive, ref, watchEffect } from 'vue';
|
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
|
||||||
import AppSelectBox from '../AppSelectBox.vue';
|
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
inheritAttrs: false,
|
|
||||||
name: 'AppSelect',
|
|
||||||
components: {
|
|
||||||
ShowDialog,
|
|
||||||
AppSelectBox
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
displayName: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: Object,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const appDg = ref();
|
|
||||||
const fieldRef=ref();
|
|
||||||
const dgIsShow = ref(false)
|
|
||||||
const selectedApp = props.modelValue && props.modelValue.app ? props.modelValue : reactive({app:{}});
|
|
||||||
const closeDg = (state: string) => {
|
|
||||||
dgIsShow.value = false;
|
|
||||||
if (state == 'OK') {
|
|
||||||
selectedApp.app = appDg.value.selected[0];
|
|
||||||
fieldRef.value.validate();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//ルール設定
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
|
||||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
|
|
||||||
const requiredExp = props.required ? [((val: any) => (!!val && !!val.app && !!val.app.name) || errmsg)] : [];
|
|
||||||
const rulesExp = [...requiredExp, ...customExp];
|
|
||||||
watchEffect(() => {
|
|
||||||
emit('update:modelValue', selectedApp);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
filter: ref(''),
|
|
||||||
dgIsShow,
|
|
||||||
appDg,
|
|
||||||
fieldRef,
|
|
||||||
closeDg,
|
|
||||||
selectedApp,
|
|
||||||
rulesExp
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-bind="$attrs">
|
|
||||||
<q-field :label="displayName" labelColor="primary" stack-label lazy-rules="ondemand" ref="fieldRef">
|
|
||||||
<template v-slot:control>
|
|
||||||
<q-card flat class="full-width">
|
|
||||||
<q-card-actions vertical>
|
|
||||||
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">クリックで設定</q-btn>
|
|
||||||
</q-card-actions>
|
|
||||||
<q-card-section class="text-caption">
|
|
||||||
<div v-if="data.dropDownApp?.name">
|
|
||||||
{{ `${data.sourceApp?.name} -> ${data.dropDownApp?.name}` }}
|
|
||||||
</div>
|
|
||||||
<div v-else>{{ placeholder }}</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
</q-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ShowDialog v-model:visible="dgIsShow" name="ドロップダウン階層化設定" @close="closeDg" min-width="50vw" min-height="20vh" disableBtn>
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-btn flat round dense icon="more_vert" >
|
|
||||||
<q-menu auto-close anchor="bottom start">
|
|
||||||
<q-list>
|
|
||||||
<q-item clickable @click="copySetting()">
|
|
||||||
<q-item-section avatar><q-icon name="content_copy" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >コピー</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
<q-item clickable @click="pasteSetting()">
|
|
||||||
<q-item-section avatar><q-icon name="content_paste" ></q-icon></q-item-section>
|
|
||||||
<q-item-section >貼り付け</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</q-list>
|
|
||||||
</q-menu>
|
|
||||||
</q-btn>
|
|
||||||
</template>
|
|
||||||
<div class="q-mb-md q-ml-md q-mr-md">
|
|
||||||
<CascadingDropDownBox v-model:model-value="data" :finishDialogHandler="finishDialogHandler" />
|
|
||||||
</div>
|
|
||||||
</ShowDialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref, watchEffect } from 'vue';
|
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
|
||||||
import CascadingDropDownBox from '../CascadingDropDownBox.vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
inheritAttrs: false,
|
|
||||||
name: 'CascadingDropDown',
|
|
||||||
components: {
|
|
||||||
ShowDialog,
|
|
||||||
CascadingDropDownBox
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
displayName: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: Object,
|
|
||||||
default: () => { ({}) }
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const dgIsShow = ref(false);
|
|
||||||
// const data = ref(props.modelValue);
|
|
||||||
const data = ref({
|
|
||||||
sourceApp: props.modelValue.sourceApp ?? { appFilter: '', showSelectApp: false },
|
|
||||||
dropDownApp: props.modelValue.dropDownApp,
|
|
||||||
fieldList: props.modelValue.fieldList ?? [],
|
|
||||||
});
|
|
||||||
const closeDg = (state: string) => {
|
|
||||||
dgIsShow.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const finishDialogHandler = (boxData) => {
|
|
||||||
data.value = boxData
|
|
||||||
dgIsShow.value = false
|
|
||||||
emit('update:modelValue', data.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
//設定をコピーする
|
|
||||||
const copySetting=()=>{
|
|
||||||
if (navigator.clipboard) {
|
|
||||||
const jsonData= JSON.stringify(data.value);
|
|
||||||
navigator.clipboard.writeText(jsonData).then(() => {
|
|
||||||
console.log('Text successfully copied to clipboard');
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
console.error('Error in copying text: ', err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.log('Clipboard API not available');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//設定を貼り付ける
|
|
||||||
const pasteSetting=async ()=>{
|
|
||||||
try {
|
|
||||||
const text = await navigator.clipboard.readText();
|
|
||||||
console.log('Text from clipboard:', text);
|
|
||||||
const jsonData=JSON.parse(text);
|
|
||||||
if('sourceApp' in jsonData && 'dropDownApp' in jsonData && 'fieldList' in jsonData){
|
|
||||||
const {sourceApp,dropDownApp, fieldList}=jsonData;
|
|
||||||
data.value.sourceApp=sourceApp;
|
|
||||||
data.value.dropDownApp=dropDownApp;
|
|
||||||
data.value.fieldList=fieldList;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to read text from clipboard: ', err);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// watchEffect(() => {
|
|
||||||
// emit('update:modelValue', data.value);
|
|
||||||
// });
|
|
||||||
|
|
||||||
return {
|
|
||||||
dgIsShow,
|
|
||||||
closeDg,
|
|
||||||
data,
|
|
||||||
finishDialogHandler,
|
|
||||||
copySetting,
|
|
||||||
pasteSetting
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="" v-bind="$attrs">
|
|
||||||
<q-field v-model="color" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label :bottom-slots="!isSelected" :rules="rulesExp">
|
|
||||||
<template v-slot:control>
|
|
||||||
<q-chip text-color="black" color="white" v-if="isSelected">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-4">
|
|
||||||
<q-avatar class="shadow-1" :style="{ background: color }" size="xs"></q-avatar>
|
|
||||||
</div>
|
|
||||||
<div class="col">
|
|
||||||
{{ color }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-chip>
|
|
||||||
</template>
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon name="colorize" class="cursor-pointer" color="primary" >
|
|
||||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
|
||||||
<q-color no-header default-view="palette" v-model="color" />
|
|
||||||
</q-popup-proxy>
|
|
||||||
</q-icon>
|
|
||||||
</template>
|
|
||||||
<template v-slot:hint>
|
|
||||||
{{ placeholder }}
|
|
||||||
</template>
|
|
||||||
</q-field>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, ref,watchEffect } from 'vue';
|
|
||||||
export default defineComponent({
|
|
||||||
inheritAttrs:false,
|
|
||||||
name: 'ColorPicker',
|
|
||||||
components: {
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
displayName: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
hint: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const color = ref(props.modelValue??"");
|
|
||||||
const isSelected = computed(()=>props.modelValue && props.modelValue!=="");
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
|
||||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
|
||||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg ),"anyColor"]:[];
|
|
||||||
const rulesExp=[...requiredExp,...customExp];
|
|
||||||
watchEffect(()=>{
|
|
||||||
emit('update:modelValue', color.value);
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
color,
|
|
||||||
isSelected,
|
|
||||||
rulesExp
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-bind="$attrs">
|
|
||||||
<q-field v-model="tree" :label="displayName" labelColor="primary" stack-label>
|
|
||||||
<template v-slot:control>
|
|
||||||
<q-card flat class="full-width">
|
|
||||||
<q-card-actions vertical>
|
|
||||||
<q-btn color="grey-3" text-color="black" :disable="btnDisable" @click="showDg()">クリックで設定:{{ isSetted ?
|
|
||||||
'設定済み' : '未設定' }}</q-btn>
|
|
||||||
</q-card-actions>
|
|
||||||
<q-card-section class="text-caption">
|
|
||||||
<div v-if="!isSetted">{{ placeholder }}</div>
|
|
||||||
<div v-else>{{ conditionString }}</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
</q-field>
|
|
||||||
<condition-editor v-model:show="show" v-model:conditionTree="tree" @closed="onClosed"></condition-editor>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { ConditionNode, ConditionTree, Operator, OperatorListItem } from 'app/src/types/Conditions';
|
|
||||||
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
|
|
||||||
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue';
|
|
||||||
import { IActionProperty } from 'src/types/ActionTypes';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'FieldInput',
|
|
||||||
inheritAttrs: false,
|
|
||||||
components: {
|
|
||||||
ConditionEditor
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
context: {
|
|
||||||
type: Array<IActionProperty>,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
displayName: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
hint: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
sourceType: {
|
|
||||||
type: String,
|
|
||||||
default: 'field'
|
|
||||||
},
|
|
||||||
connectProps:{
|
|
||||||
type:Object,
|
|
||||||
default:()=>({})
|
|
||||||
},
|
|
||||||
onlySourceSelect: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
operatorList: {
|
|
||||||
type: Array,
|
|
||||||
},
|
|
||||||
inputConfig: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({
|
|
||||||
left: {
|
|
||||||
canInput: false,
|
|
||||||
buttonsConfig: [
|
|
||||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
|
||||||
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
right: {
|
|
||||||
canInput: true,
|
|
||||||
buttonsConfig: [
|
|
||||||
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
let source = reactive(props.connectProps["source"]);
|
|
||||||
if(!source){
|
|
||||||
source = props.context.find(element => element.props.name === 'sources');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source) {
|
|
||||||
if (props.sourceType === 'field') {
|
|
||||||
provide('sourceFields', computed(() => source.props?.modelValue?.fields ?? []));
|
|
||||||
} else if (props.sourceType === 'app') {
|
|
||||||
provide('sourceApp', computed(() => source.props?.modelValue?.app?.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
provide('leftDynamicItemConfig', props.inputConfig.left);
|
|
||||||
provide('rightDynamicItemConfig', props.inputConfig.right);
|
|
||||||
provide('Operator', props.operatorList);
|
|
||||||
|
|
||||||
const btnDisable = computed(() => {
|
|
||||||
const onlySourceSelect = props.onlySourceSelect;
|
|
||||||
|
|
||||||
if (!onlySourceSelect) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.sourceType === 'field') {
|
|
||||||
return source?.props?.modelValue?.fields?.length ?? 0 > 0;
|
|
||||||
} else if (props.sourceType === 'app') {
|
|
||||||
return source?.props?.modelValue?.app?.id ? false : true
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
|
|
||||||
const appDg = ref();
|
|
||||||
const show = ref(false);
|
|
||||||
const tree = reactive(new ConditionTree());
|
|
||||||
if (props.modelValue && props.modelValue !== '') {
|
|
||||||
tree.fromJson(props.modelValue);
|
|
||||||
} else {
|
|
||||||
const newNode = new ConditionNode({}, (props.operatorList && props.operatorList.length > 0) ? props.operatorList[0] as OperatorListItem : Operator.Equal, '', tree.root);
|
|
||||||
tree.addNode(tree.root, newNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSetted = ref(props.modelValue && props.modelValue !== '');
|
|
||||||
|
|
||||||
const conditionString = computed(() => {
|
|
||||||
const condiStr= tree.buildConditionString(tree.root);
|
|
||||||
return condiStr==='()'?'(条件なし)':condiStr;
|
|
||||||
});
|
|
||||||
|
|
||||||
const showDg = () => {
|
|
||||||
show.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClosed = (val: string) => {
|
|
||||||
if (val == 'OK') {
|
|
||||||
isSetted.value = true;
|
|
||||||
tree.setQuery(tree.buildConditionQueryString(tree.root));
|
|
||||||
const conditionJson = tree.toJson();
|
|
||||||
emit('update:modelValue', conditionJson);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
tree.setQuery(tree.buildConditionQueryString(tree.root));
|
|
||||||
const conditionJson = tree.toJson();
|
|
||||||
emit('update:modelValue', conditionJson);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
appDg,
|
|
||||||
isSetted,
|
|
||||||
show,
|
|
||||||
showDg,
|
|
||||||
onClosed,
|
|
||||||
tree,
|
|
||||||
conditionString,
|
|
||||||
btnDisable
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,322 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="q-my-md" v-bind="$attrs">
|
|
||||||
<q-field :label="displayName" labelColor="primary" stack-label
|
|
||||||
v-model="mappingProps"
|
|
||||||
:rules="rulesExp"
|
|
||||||
ref="fieldRef"
|
|
||||||
>
|
|
||||||
<template v-slot:control>
|
|
||||||
<q-card flat class="full-width">
|
|
||||||
<q-card-actions vertical>
|
|
||||||
<q-btn color="grey-3" text-color="black" :disable="btnDisable"
|
|
||||||
@click="() => { dgIsShow = true }">クリックで設定</q-btn>
|
|
||||||
</q-card-actions>
|
|
||||||
<q-card-section class="text-caption">
|
|
||||||
<div v-if="mappingObjectsInputDisplay && mappingObjectsInputDisplay.length > 0">
|
|
||||||
<div v-for="(item) in mappingObjectsInputDisplay" :key="item">{{ item }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-else>{{ placeholder }}</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
</q-field>
|
|
||||||
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="55vw" min-height="60vh">
|
|
||||||
<div class="">
|
|
||||||
<div class="row q-col-gutter-x-xs flex-center">
|
|
||||||
<div class="col-5">
|
|
||||||
<div class="q-mx-xs">ソース</div>
|
|
||||||
</div>
|
|
||||||
<!-- <div class="col-1">
|
|
||||||
</div> -->
|
|
||||||
<div class="col-5">
|
|
||||||
<div class="row justify-between q-mr-md">
|
|
||||||
<div class="">{{ sourceApp?.name }}</div>
|
|
||||||
<q-btn outline color="primary" size="xs" label="最新のフィールドを取得する"
|
|
||||||
@click="() => updateFields(sourceAppId!)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-1 q-pl-sm">
|
|
||||||
キー
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-virtual-scroll style="max-height: 60vh;" :items="mappingProps.data" separator v-slot="{ item, index }">
|
|
||||||
<!-- <div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id"> -->
|
|
||||||
<div class="row q-pa-sm q-col-gutter-x-md flex-center">
|
|
||||||
<div class="col-5">
|
|
||||||
<ConditionObject :config="config" v-model="item.from" :disabled="item.disabled"
|
|
||||||
:label="item.disabled ? '「Lookup」によってロックされる' : undefined" />
|
|
||||||
</div>
|
|
||||||
<!-- <div class="col-1">
|
|
||||||
</div> -->
|
|
||||||
<div class="col-5">
|
|
||||||
<q-field v-model="item.vName" type="text" outlined dense :disable="item.disabled" >
|
|
||||||
<!-- <template v-slot:append>
|
|
||||||
<q-icon name="search" class="cursor-pointer"
|
|
||||||
@click="() => { mappingProps[index].to.isDialogVisible = true }" />
|
|
||||||
</template> -->
|
|
||||||
<template v-slot:control>
|
|
||||||
<div class="self-center full-width no-outline" tabindex="0"
|
|
||||||
v-if="item.to.app?.name && item.to.fields?.length > 0 && item.to.fields[0].label">
|
|
||||||
{{ `${item.to.fields[0].label}` }}
|
|
||||||
<span class="text-red" v-if="item.to.fields[0].required">*</span>
|
|
||||||
<q-tooltip class="bg-yellow-2 text-black shadow-4" >
|
|
||||||
<div>アプリ : {{ item.to.app.name }}</div>
|
|
||||||
<div>フィールドのコード : {{ item.to.fields[0].code }}</div>
|
|
||||||
<div>フィールドのタイプ : {{ item.to.fields[0].type }}</div>
|
|
||||||
<div v-if="item.to.fields[0].required">必須項目</div>
|
|
||||||
<!-- <div>フィールド : {{ item.to.fields[0] }}</div>
|
|
||||||
<div>フィールド : {{ item.isKey }}</div> -->
|
|
||||||
</q-tooltip>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</q-field>
|
|
||||||
</div>
|
|
||||||
<div class="col-1">
|
|
||||||
<q-checkbox size="sm" v-model="item.isKey" :disable="item.disabled" />
|
|
||||||
<!-- <q-btn flat round dense icon="delete" size="sm" @click="() => deleteMappingObject(index)" /> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<show-dialog v-model:visible="mappingProps.data[index].to.isDialogVisible" name="フィールド一覧"
|
|
||||||
@close="closeToDg" ref="fieldDlg">
|
|
||||||
<FieldSelect v-if="onlySourceSelect" ref="fieldDlg" name="フィールド" :appId="sourceAppId" not_page
|
|
||||||
:selectedFields="mappingProps.data[index].to.fields"
|
|
||||||
:updateSelects="(fields) => { mappingProps.data[index].to.fields = fields; mappingProps.data[index].to.app = sourceApp }">
|
|
||||||
</FieldSelect>
|
|
||||||
<AppFieldSelectBox v-else v-model:selectedField="mappingProps.data[index].to" />
|
|
||||||
</show-dialog>
|
|
||||||
<!-- </div> -->
|
|
||||||
</q-virtual-scroll>
|
|
||||||
|
|
||||||
<div class="q-mt-lg q-ml-md row ">
|
|
||||||
<q-checkbox size="sm" v-model="mappingProps.createWithNull" label="キーが存在しない場合は新規に作成され、存在する場合はデータが更新されます。" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</show-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { computed, defineComponent, watch, isRef, reactive, ref, watchEffect } from 'vue';
|
|
||||||
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
|
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
|
||||||
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
|
||||||
import FieldSelect from '../FieldSelect.vue';
|
|
||||||
import { IApp, IField } from './AppFieldSelect.vue';
|
|
||||||
import { api } from 'boot/axios';
|
|
||||||
|
|
||||||
type ContextProps = {
|
|
||||||
props?: {
|
|
||||||
name: string;
|
|
||||||
modelValue?: {
|
|
||||||
app: {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface IMappingSetting {
|
|
||||||
data: IMappingValueType[];
|
|
||||||
createWithNull: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IMappingValueType {
|
|
||||||
id: string;
|
|
||||||
from: { sharedText?: string };
|
|
||||||
to: {
|
|
||||||
app?: IApp,
|
|
||||||
fields: IField[],
|
|
||||||
isDialogVisible: boolean;
|
|
||||||
};
|
|
||||||
isKey: boolean;
|
|
||||||
disabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const blackListLabelName = ['レコード番号', '作業者', '更新者', '更新日時', '作成日時', '作成者']
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'DataMapping',
|
|
||||||
inheritAttrs: false,
|
|
||||||
components: {
|
|
||||||
ShowDialog,
|
|
||||||
ConditionObject,
|
|
||||||
AppFieldSelectBox,
|
|
||||||
FieldSelect
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
context: {
|
|
||||||
type: Array<ContextProps>,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
displayName: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: Object as () => IMappingSetting,
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
onlySourceSelect: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
fieldTypes:{
|
|
||||||
type:Array,
|
|
||||||
default:()=>[]
|
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const fieldRef=ref();
|
|
||||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
|
||||||
|
|
||||||
const sourceApp = computed(() => source?.props?.modelValue?.app);
|
|
||||||
|
|
||||||
const sourceAppId = computed(() => sourceApp.value?.id);
|
|
||||||
|
|
||||||
//ルール設定
|
|
||||||
const checkMapping = (val:IMappingSetting)=>{
|
|
||||||
if(!val || !val.data){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
console.log(val);
|
|
||||||
const mappingDatas = val.data.filter(item=>item.from?.sharedText && item.to.fields?.length > 0);
|
|
||||||
return mappingDatas.length>0;
|
|
||||||
}
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
|
||||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
|
|
||||||
const requiredExp = props.required ? [((val: any) => checkMapping(val) || errmsg)] : [];
|
|
||||||
const rulesExp = [...requiredExp, ...customExp];
|
|
||||||
|
|
||||||
// const mappingProps = ref(props.modelValue?.data ?? []);
|
|
||||||
|
|
||||||
// const createWithNull = ref(props.modelValue?.createWithNull ?? false);
|
|
||||||
|
|
||||||
const mappingProps = reactive<IMappingSetting>({
|
|
||||||
data:props.modelValue?.data ?? [],
|
|
||||||
createWithNull:props.modelValue?.createWithNull ?? false
|
|
||||||
});
|
|
||||||
|
|
||||||
const closeDg = () => {
|
|
||||||
fieldRef.value.validate();
|
|
||||||
emit('update:modelValue',mappingProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeToDg = () => {
|
|
||||||
emit('update:modelValue',mappingProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 外部ソースコンポーネントの appid をリッスンし、変更されたときに現在のコンポーネントを更新します
|
|
||||||
watch(() => sourceAppId.value, async (newId,) => {
|
|
||||||
if (!newId) return;
|
|
||||||
updateFields(newId)
|
|
||||||
})
|
|
||||||
|
|
||||||
const updateFields = async (sourceAppId: string) => {
|
|
||||||
const ktAppFields = await api.get('api/v1/appfields', {
|
|
||||||
params: {
|
|
||||||
app: sourceAppId
|
|
||||||
}
|
|
||||||
}).then(res => {
|
|
||||||
return Object.values(res.data.properties)
|
|
||||||
// kintoneのデフォルトの非表示フィールドフィルタリング
|
|
||||||
.filter(f => !blackListLabelName.find(label => f.label === label))
|
|
||||||
.map(f => ({ name: f.label, objectType: 'field', ...f }))
|
|
||||||
.map(f => {
|
|
||||||
// 更新前の値を求める
|
|
||||||
const beforeData = mappingProps.data.find(m => m.to.fields[0].code === f.code)
|
|
||||||
return {
|
|
||||||
id: uuidv4(),
|
|
||||||
from: beforeData?.from ?? {}, // 以前のデータを入力します
|
|
||||||
to: {
|
|
||||||
app: sourceApp.value,
|
|
||||||
fields: [f],
|
|
||||||
isDialogVisible: false
|
|
||||||
},
|
|
||||||
isKey: beforeData?.isKey ?? false, // 以前のデータを入力します
|
|
||||||
disabled: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// 「ルックアップ」によってロックされているフィールドを検索する
|
|
||||||
const lookupFixedField = ktAppFields
|
|
||||||
.filter(field => field.to.fields[0].lookup !== undefined)
|
|
||||||
.flatMap(field => field.to.fields[0].lookup.fieldMappings.map((m) => m.field))
|
|
||||||
|
|
||||||
// 「ルックアップ」でロックされたビューコンポーネントを非対話型に設定します
|
|
||||||
if (lookupFixedField.length > 0) {
|
|
||||||
ktAppFields.filter(f => lookupFixedField.includes(f.to.fields[0].code)).forEach(f => f.disabled = true)
|
|
||||||
}
|
|
||||||
|
|
||||||
mappingProps.data = ktAppFields
|
|
||||||
}
|
|
||||||
|
|
||||||
const mappingObjectsInputDisplay = computed(() =>
|
|
||||||
(mappingProps.data && Array.isArray(mappingProps.data)) ?
|
|
||||||
mappingProps.data
|
|
||||||
.filter(item => item.from?.sharedText && item.to.fields?.length > 0)
|
|
||||||
.map(item => {
|
|
||||||
return `field(${item.to.app?.id},${item.to.fields[0].label}) = ${item.from.sharedText} `;
|
|
||||||
})
|
|
||||||
: []
|
|
||||||
);
|
|
||||||
|
|
||||||
const btnDisable = computed(() => props.onlySourceSelect ? !(source?.props?.modelValue?.app?.id) : false);
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
emit('update:modelValue', mappingProps);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
uuidv4,
|
|
||||||
dgIsShow: ref(false),
|
|
||||||
fieldRef,
|
|
||||||
closeDg,
|
|
||||||
toDgIsShow: ref(false),
|
|
||||||
closeToDg,
|
|
||||||
mappingProps,
|
|
||||||
updateFields,
|
|
||||||
// addMappingObject: () => mappingProps.push(defaultMappingProp()),
|
|
||||||
// deleteMappingObject,
|
|
||||||
mappingObjectsInputDisplay,
|
|
||||||
sourceApp,
|
|
||||||
sourceAppId,
|
|
||||||
btnDisable,
|
|
||||||
rulesExp,
|
|
||||||
checkMapping,
|
|
||||||
config: {
|
|
||||||
canInput: false,
|
|
||||||
buttonsConfig: [
|
|
||||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
|
||||||
{ label: '変数', color: 'green', type: 'VariableAdd', editable: false },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<style lang="scss"></style>
|
|
||||||
@@ -1,275 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<q-field :label="displayName" labelColor="primary" stack-label
|
|
||||||
v-model="processingProps"
|
|
||||||
:rules="rulesExp"
|
|
||||||
lazy-rules="ondemand"
|
|
||||||
ref="fieldRef"
|
|
||||||
>
|
|
||||||
<template v-slot:control>
|
|
||||||
<q-card flat class="full-width">
|
|
||||||
<q-card-actions vertical>
|
|
||||||
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">クリックで設定</q-btn>
|
|
||||||
</q-card-actions>
|
|
||||||
<q-card-section class="text-caption">
|
|
||||||
<div v-if="processingObjectsInputDisplay && processingObjectsInputDisplay.length>0">
|
|
||||||
<div v-for="(item) in processingObjectsInputDisplay" :key="item">{{ item }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-else>{{ placeholder }}</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</template>
|
|
||||||
</q-field>
|
|
||||||
<show-dialog v-model:visible="dgIsShow" name="集計処理" @close="closeDg" min-width="50vw" min-height="60vh">
|
|
||||||
<div class="q-mx-md q-mb-md">
|
|
||||||
<q-input v-model="processingProps.name" type="text" label-color="primary" label="集計結果の変数名"
|
|
||||||
placeholder="集計結果を格納する変数名を入力してください" stack-label />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="q-mx-md">
|
|
||||||
<div class="row q-col-gutter-x-xs flex-center">
|
|
||||||
<div class="col-5">
|
|
||||||
<div class="q-mx-xs">データソース</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<div class="q-mx-xs">集計計算</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-4">
|
|
||||||
<div class="q-mx-xs">集計結果変数名</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-1"><q-btn flat round dense icon="add" size="sm" @click="addProcessingObject" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="q-my-sm" v-for="(item, index) in processingObjects" :key="item.id">
|
|
||||||
<div class="row q-col-gutter-x-xs flex-center">
|
|
||||||
<div class="col-5">
|
|
||||||
<ConditionObject v-model="item.field" />
|
|
||||||
</div>
|
|
||||||
<div class="col-2 q-pa-sm">
|
|
||||||
<q-select v-model="item.logicalOperator" :options="logicalOperators" outlined dense></q-select>
|
|
||||||
</div>
|
|
||||||
<div class="col-4">
|
|
||||||
<q-input v-model="item.vName" type="text" outlined dense />
|
|
||||||
</div>
|
|
||||||
<div class="col-1">
|
|
||||||
<q-btn flat round dense icon="delete" size="sm" @click="() => deleteProcessingObject(index)" />
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</show-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
|
|
||||||
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
|
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
props?: {
|
|
||||||
name: string;
|
|
||||||
modelValue?: {
|
|
||||||
fields: {
|
|
||||||
type: string;
|
|
||||||
label: string;
|
|
||||||
code: string;
|
|
||||||
}[]
|
|
||||||
} | string
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
type ProcessingObjectType = {
|
|
||||||
field?: {
|
|
||||||
sharedText: string;
|
|
||||||
objectType: 'field';
|
|
||||||
};
|
|
||||||
logicalOperator?: string;
|
|
||||||
vName?: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ValueType = {
|
|
||||||
name: string;
|
|
||||||
actionName: string,
|
|
||||||
displayName: string,
|
|
||||||
vars: ProcessingObjectType[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'DataProcessing',
|
|
||||||
inheritAttrs: false,
|
|
||||||
components: {
|
|
||||||
ShowDialog,
|
|
||||||
ConditionObject,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
context: {
|
|
||||||
type: Array<Props>,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
displayName: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: Object as () => ValueType,
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const fieldRef=ref();
|
|
||||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
|
||||||
|
|
||||||
if (source) {
|
|
||||||
provide('sourceFields', computed(() => {
|
|
||||||
const modelValue = source.props?.modelValue;
|
|
||||||
if (modelValue && typeof modelValue !== 'string') {
|
|
||||||
return modelValue.fields;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}));
|
|
||||||
provide('sourceApp', computed(() => source.props?.modelValue?.app?.id));
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionName = props.context.find(element => element?.props?.name === 'displayName')
|
|
||||||
|
|
||||||
const processingProps: ValueType = props.modelValue && props.modelValue.vars
|
|
||||||
? reactive(props.modelValue)
|
|
||||||
: reactive({
|
|
||||||
name: '',
|
|
||||||
actionName: actionName?.props?.modelValue as string,
|
|
||||||
displayName: '結果(戻り値)',
|
|
||||||
vars: [
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
field:{
|
|
||||||
objectType:'field',
|
|
||||||
sharedText:''
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
const closeDg = () => {
|
|
||||||
fieldRef.value.validate();
|
|
||||||
emit('update:modelValue', processingProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
const processingObjects = processingProps.vars;
|
|
||||||
|
|
||||||
const deleteProcessingObject = (index: number) => {
|
|
||||||
if(processingObjects.length >0){
|
|
||||||
processingObjects.splice(index, 1);
|
|
||||||
}
|
|
||||||
if(processingObjects.length===0){
|
|
||||||
addProcessingObject();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const processingObjectsInputDisplay = computed(() =>
|
|
||||||
processingObjects ?
|
|
||||||
processingObjects
|
|
||||||
.filter(item => item.field && item.logicalOperator && item.vName)
|
|
||||||
.map(item => {
|
|
||||||
return`var(${processingProps.name}.${item.vName}) = ${item.field?.sharedText}`
|
|
||||||
})
|
|
||||||
: []
|
|
||||||
);
|
|
||||||
|
|
||||||
const addProcessingObject=()=>{
|
|
||||||
processingObjects.push({
|
|
||||||
id: uuidv4(),
|
|
||||||
field:{
|
|
||||||
objectType:'field',
|
|
||||||
sharedText:''
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//集計処理方法
|
|
||||||
const logicalOperators = ref([
|
|
||||||
{
|
|
||||||
"operator": "",
|
|
||||||
"label": "なし"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operator": "SUM",
|
|
||||||
"label": "合計"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operator": "AVG",
|
|
||||||
"label": "平均"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operator": "MAX",
|
|
||||||
"label": "最大値"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operator": "MIN",
|
|
||||||
"label": "最小値"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operator": "COUNT",
|
|
||||||
"label": "カウント"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"operator": "FIRST",
|
|
||||||
"label": "最初の値"
|
|
||||||
}
|
|
||||||
]);
|
|
||||||
const checkInput=(val:ValueType)=>{
|
|
||||||
if(!val){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if(!val.name){
|
|
||||||
return "集計結果の変数名を入力してください";
|
|
||||||
}
|
|
||||||
if(!val.vars || val.vars.length==0){
|
|
||||||
return "集計処理を設定してください";
|
|
||||||
}
|
|
||||||
if(val.vars.some((x)=>!x.vName)){
|
|
||||||
return "集計結果変数名を入力してください";
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
|
||||||
const requiredExp = props.required ? [(val: any) => checkInput(val)] : [];
|
|
||||||
const rulesExp = [...requiredExp, ...customExp];
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
emit('update:modelValue', processingProps);
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
uuidv4,
|
|
||||||
dgIsShow: ref(false),
|
|
||||||
closeDg,
|
|
||||||
processingObjects,
|
|
||||||
processingProps,
|
|
||||||
addProcessingObject,
|
|
||||||
deleteProcessingObject,
|
|
||||||
logicalOperators,
|
|
||||||
processingObjectsInputDisplay,
|
|
||||||
rulesExp,
|
|
||||||
fieldRef
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss"></style>
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-bind="$attrs">
|
|
||||||
<q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" label-color="primary" mask="date" :rules="rulesExp" stack-label>
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon name="event" class="cursor-pointer">
|
|
||||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
|
||||||
<q-date v-model="selectedDate">
|
|
||||||
<div class="row items-center justify-end">
|
|
||||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
|
||||||
</div>
|
|
||||||
</q-date>
|
|
||||||
</q-popup-proxy>
|
|
||||||
</q-icon>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref ,watchEffect} from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'DatePicker',
|
|
||||||
inheritAttrs:false,
|
|
||||||
props: {
|
|
||||||
displayName:{
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name:{
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
hint:{
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const selectedDate = ref(props.modelValue);
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
|
||||||
const requiredExp = props.required?[((val:any)=>!!val||`${props.displayName}が必須です。`),'date']:['date'];
|
|
||||||
const rulesExp=[...requiredExp,...customExp];
|
|
||||||
watchEffect(() => {
|
|
||||||
emit('update:modelValue', selectedDate.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
selectedDate,
|
|
||||||
rulesExp
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-bind="$attrs">
|
|
||||||
<q-input :label="displayName" v-model="inputValue" label-color="primary"
|
|
||||||
:placeholder="placeholder"
|
|
||||||
:rules="rulesExp"
|
|
||||||
stack-label>
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-btn round dense flat icon="add" @click="addButtonEvent()" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent,ref,watchEffect } from 'vue';
|
|
||||||
import { useFlowEditorStore } from '../../stores/flowEditor';
|
|
||||||
import { IKintoneEventGroup,kintoneEvent } from 'src/types/KintoneEvents';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'EventSetter',
|
|
||||||
inheritAttrs:false,
|
|
||||||
props: {
|
|
||||||
displayName:{
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name:{
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
hint:{
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
connectProps:{
|
|
||||||
type:Object,
|
|
||||||
default:undefined
|
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props , { emit }) {
|
|
||||||
const inputValue = ref(props.modelValue);
|
|
||||||
const store = useFlowEditorStore();
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
|
||||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を入力してください。`;
|
|
||||||
const requiredExp = props.required ? [((val: any) => !!val || errmsg)] : [];
|
|
||||||
const rulesExp = [...requiredExp, ...customExp];
|
|
||||||
const addButtonEvent=()=>{
|
|
||||||
const eventId =store.currentFlow?.getRoot()?.name;
|
|
||||||
if(eventId===undefined){return;}
|
|
||||||
let displayName = inputValue.value;
|
|
||||||
if(props.connectProps!==undefined && "displayName" in props.connectProps){
|
|
||||||
displayName =props.connectProps["displayName"].props.modelValue;
|
|
||||||
}
|
|
||||||
const customButtonId=`${eventId}.customButtonClick`;
|
|
||||||
const findedEvent = store.eventTree.findEventById(customButtonId);
|
|
||||||
if(findedEvent && "events" in findedEvent){
|
|
||||||
const customEvents = findedEvent as IKintoneEventGroup;
|
|
||||||
const addEventId = customButtonId+"." + inputValue.value;
|
|
||||||
if(store.eventTree.findEventById(addEventId)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
customEvents.events.push(new kintoneEvent(
|
|
||||||
displayName,
|
|
||||||
addEventId,
|
|
||||||
customButtonId,
|
|
||||||
'DELETABLE'
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
watchEffect(() => {
|
|
||||||
emit('update:modelValue', inputValue.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
inputValue,
|
|
||||||
addButtonEvent,
|
|
||||||
rulesExp
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-bind="$attrs">
|
|
||||||
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
|
|
||||||
:bottom-slots="!isSelected"
|
|
||||||
:rules="rulesExp"
|
|
||||||
>
|
|
||||||
<template v-slot:control>
|
|
||||||
<q-chip color="primary" text-color="white" v-if="isSelected">
|
|
||||||
{{ selectedField.name }}
|
|
||||||
</q-chip>
|
|
||||||
</template>
|
|
||||||
<template v-slot:hint v-if="!isSelected">
|
|
||||||
{{ placeholder }}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
|
||||||
</template>
|
|
||||||
</q-field>
|
|
||||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" min-width="400px">
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
<field-select ref="appDg" name="フィールド" :type="selectType" :appId="store.appInfo?.appId" :selectedFields="selectedFields" :fieldTypes="fieldTypes" :filter="filter"></field-select>
|
|
||||||
</show-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref, watchEffect, computed } from 'vue';
|
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
|
||||||
import FieldSelect from '../FieldSelect.vue';
|
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
|
||||||
interface IField {
|
|
||||||
name: string,
|
|
||||||
code: string,
|
|
||||||
type: string
|
|
||||||
}
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'FieldInput',
|
|
||||||
inheritAttrs:false,
|
|
||||||
components: {
|
|
||||||
ShowDialog,
|
|
||||||
FieldSelect,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
displayName: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
selectType:{
|
|
||||||
type:String,
|
|
||||||
default:'single'
|
|
||||||
},
|
|
||||||
fieldTypes:{
|
|
||||||
type:Array,
|
|
||||||
default:()=>[]
|
|
||||||
},
|
|
||||||
hint: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: Object,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const appDg = ref();
|
|
||||||
const show = ref(false);
|
|
||||||
const selectedField = ref(props.modelValue);
|
|
||||||
const selectedFields =computed(()=>!selectedField.value?[]: [selectedField.value]);
|
|
||||||
const store = useFlowEditorStore();
|
|
||||||
const isSelected = computed(() => {
|
|
||||||
return selectedField.value !== null && typeof selectedField.value === 'object' && ('name' in selectedField.value)
|
|
||||||
});
|
|
||||||
//ルール設定
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
|
||||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
|
||||||
const requiredExp = props.required ? [((val: any) => (!!val && typeof val==='object' && !!val.name) || errmsg)] : [];
|
|
||||||
const rulesExp = [...requiredExp, ...customExp];
|
|
||||||
|
|
||||||
const showDg = () => {
|
|
||||||
show.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeDg = (val: string) => {
|
|
||||||
if (val == 'OK') {
|
|
||||||
selectedField.value = appDg.value.selected[0];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
emit('update:modelValue', selectedField.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
store,
|
|
||||||
appDg,
|
|
||||||
show,
|
|
||||||
showDg,
|
|
||||||
closeDg,
|
|
||||||
selectedField,
|
|
||||||
isSelected,
|
|
||||||
filter:ref(''),
|
|
||||||
selectedFields,
|
|
||||||
rulesExp
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-bind="$attrs">
|
|
||||||
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label
|
|
||||||
:rules="rulesExp" :maxlength="maxLength">
|
|
||||||
<template v-slot:append v-if="hint !== ''">
|
|
||||||
<q-icon name="help" size="22px" color="blue-8">
|
|
||||||
<q-tooltip class="bg-yellow-2 text-black shadow-4" anchor="bottom right">
|
|
||||||
<div class="hint-text" v-html="hint" />
|
|
||||||
</q-tooltip>
|
|
||||||
</q-icon>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref, watchEffect, computed } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'InputText',
|
|
||||||
inheritAttrs: false,
|
|
||||||
props: {
|
|
||||||
displayName: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
fieldTypes:{
|
|
||||||
type:Array,
|
|
||||||
default:()=>[]
|
|
||||||
},
|
|
||||||
hint: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
maxLength: {
|
|
||||||
type: Number,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: null as any,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const inputValue = computed({
|
|
||||||
get: () => {
|
|
||||||
if (props.modelValue !== null && typeof props.modelValue === 'object' && 'name' in props.modelValue) {
|
|
||||||
return props.modelValue.name;
|
|
||||||
} else {
|
|
||||||
return props.modelValue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
set: (val) => {
|
|
||||||
if (props.name === 'verName') {
|
|
||||||
// return props.modelValue.name;
|
|
||||||
emit('update:modelValue', { name: val });
|
|
||||||
} else {
|
|
||||||
emit('update:modelValue', val);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// const inputValue = ref(props.modelValue);
|
|
||||||
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
|
||||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
|
||||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
|
||||||
const rulesExp=[...requiredExp,...customExp];
|
|
||||||
// const finalValue = computed(() => {
|
|
||||||
// return props.name !== 'verName' ? inputValue.value : {
|
|
||||||
// name: inputValue.value,
|
|
||||||
// };
|
|
||||||
// });
|
|
||||||
// watchEffect(() => {
|
|
||||||
// emit('update:modelValue', finalValue);
|
|
||||||
// });
|
|
||||||
|
|
||||||
return {
|
|
||||||
inputValue,
|
|
||||||
showhint: ref(false),
|
|
||||||
rulesExp
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
.hint-text {
|
|
||||||
white-space: always;
|
|
||||||
max-width: 450px;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-bind="$attrs">
|
|
||||||
<q-input :label="displayName" label-color="primary" v-model="inputValue"
|
|
||||||
:placeholder="placeholder"
|
|
||||||
:rules="rulesExp"
|
|
||||||
autogrow
|
|
||||||
stack-label />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent, ref, watchEffect } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'MuiltInputText',
|
|
||||||
inheritAttrs: false,
|
|
||||||
props: {
|
|
||||||
displayName: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
hint: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const inputValue = ref(props.modelValue);
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
|
||||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
|
||||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
|
||||||
const rulesExp=[...requiredExp,...customExp];
|
|
||||||
watchEffect(() => {
|
|
||||||
emit('update:modelValue', inputValue.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
inputValue,
|
|
||||||
rulesExp
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="" v-bind="$attrs">
|
|
||||||
<q-input v-model.number="numValue" type="number" :label="displayName" label-color="primary" stack-label bottom-slots
|
|
||||||
:min="min"
|
|
||||||
:max="max"
|
|
||||||
:rules="rulesExp"
|
|
||||||
>
|
|
||||||
<template v-slot:hint>
|
|
||||||
{{ placeholder }}
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { computed, defineComponent, ref, watchEffect } from 'vue';
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'NumInput',
|
|
||||||
inheritAttrs:false,
|
|
||||||
components: {
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
displayName: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
hint: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
min:{
|
|
||||||
type:Number,
|
|
||||||
default:undefined
|
|
||||||
},
|
|
||||||
max:{
|
|
||||||
type:Number,
|
|
||||||
default:undefined
|
|
||||||
},
|
|
||||||
//[val=>!!val ||'数値を入力してください',val=>val<=100 && val>=1 || '1-100の範囲内の数値を入力してください']
|
|
||||||
rules:{
|
|
||||||
type:String,
|
|
||||||
default:undefined
|
|
||||||
},
|
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: [Number , String],
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const numValue = ref(props.modelValue);
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
|
||||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
|
||||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
|
||||||
const rulesExp=[...requiredExp,...customExp];
|
|
||||||
|
|
||||||
watchEffect(()=>{
|
|
||||||
emit("update:modelValue",numValue.value);
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
numValue,
|
|
||||||
rulesExp
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div v-for="(item, index) in properties" :key="index" >
|
|
||||||
<component :is="item.component" v-bind="item.props" :context="properties" :connectProps="connectProps(item.props)" v-model="item.props.modelValue"></component>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
/**
|
|
||||||
* プロパティ属性設定生成する
|
|
||||||
*/
|
|
||||||
import { PropType, defineComponent,ref } from 'vue';
|
|
||||||
import InputText from '../right/InputText.vue';
|
|
||||||
import SelectBox from '../right/SelectBox.vue';
|
|
||||||
import DatePicker from '../right/DatePicker.vue';
|
|
||||||
import FieldInput from '../right/FieldInput.vue';
|
|
||||||
import AppFieldSelect from './AppFieldSelect.vue';
|
|
||||||
import MuiltInputText from '../right/MuiltInputText.vue';
|
|
||||||
import ConditionInput from '../right/ConditionInput.vue';
|
|
||||||
import EventSetter from '../right/EventSetter.vue';
|
|
||||||
import ColorPicker from './ColorPicker.vue';
|
|
||||||
import NumInput from './NumInput.vue';
|
|
||||||
import DataProcessing from './DataProcessing.vue';
|
|
||||||
import DataMapping from './DataMapping.vue';
|
|
||||||
import AppSelect from './AppSelect.vue';
|
|
||||||
import CascadingDropDown from './CascadingDropDown.vue';
|
|
||||||
import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'PropertyList',
|
|
||||||
components: {
|
|
||||||
InputText,
|
|
||||||
SelectBox,
|
|
||||||
DatePicker,
|
|
||||||
FieldInput,
|
|
||||||
AppFieldSelect,
|
|
||||||
MuiltInputText,
|
|
||||||
ConditionInput,
|
|
||||||
EventSetter,
|
|
||||||
ColorPicker,
|
|
||||||
NumInput,
|
|
||||||
DataProcessing,
|
|
||||||
DataMapping,
|
|
||||||
AppSelect,
|
|
||||||
CascadingDropDown
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
nodeProps: {
|
|
||||||
type: Object as PropType<Array<IActionProperty>>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
jsonValue:{
|
|
||||||
type: Object,
|
|
||||||
required: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setup(props, context) {
|
|
||||||
const properties=ref(props.nodeProps);
|
|
||||||
const connectProps=(props:IProp)=>{
|
|
||||||
const connProps:any={context:properties};
|
|
||||||
if(props && "connectProps" in props && props.connectProps!=undefined){
|
|
||||||
for(let connProp of props.connectProps){
|
|
||||||
let targetProp = properties.value.find((prop)=>prop.props.name===connProp.propName);
|
|
||||||
if(targetProp){
|
|
||||||
connProps[connProp.key]=targetProp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return connProps;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
properties,
|
|
||||||
connectProps
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
</style>
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="q-pa-md q-gutter-sm">
|
|
||||||
<q-drawer
|
|
||||||
side="right"
|
|
||||||
:show-if-above="false"
|
|
||||||
bordered
|
|
||||||
:width="301"
|
|
||||||
:breakpoint="500"
|
|
||||||
class="bg-grey-3"
|
|
||||||
:model-value="showPanel"
|
|
||||||
elevated
|
|
||||||
overlay
|
|
||||||
>
|
|
||||||
<q-form @submit="save" autocomplete="off" class="full-height">
|
|
||||||
<q-card class="column" style="max-width: 300px;min-height: 100%">
|
|
||||||
<q-card-section>
|
|
||||||
<div class="text-h6">{{ actionNode?.subTitle }}:設定</div>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="col q-pt-none">
|
|
||||||
<property-list :node-props="actionProps" v-if="showPanel" ></property-list>
|
|
||||||
</q-card-section>
|
|
||||||
|
|
||||||
<q-card-actions align="right" class="bg-white text-teal">
|
|
||||||
<q-btn flat label="キャンセル" @click="cancel" outline dense padding="none sm" color="primary"/>
|
|
||||||
<q-btn flat label="更新" type="submit" outline dense padding="none sm" color="primary" />
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
|
||||||
</q-form>
|
|
||||||
</q-drawer>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { ref,defineComponent, PropType ,watchEffect} from 'vue'
|
|
||||||
import PropertyList from 'components/right/PropertyList.vue';
|
|
||||||
import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'PropertyPanel',
|
|
||||||
components: {
|
|
||||||
PropertyList
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
actionNode:{
|
|
||||||
type:Object as PropType<IActionNode>,
|
|
||||||
required:true
|
|
||||||
},
|
|
||||||
drawerRight:{
|
|
||||||
type:Boolean,
|
|
||||||
required:true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
emits: [
|
|
||||||
'update:drawerRight',
|
|
||||||
'saveActionProps'
|
|
||||||
],
|
|
||||||
setup(props,{emit}) {
|
|
||||||
const showPanel =ref(props.drawerRight);
|
|
||||||
|
|
||||||
const cloneProps = (actionProps:IActionProperty[]):IActionProperty[]|null=>{
|
|
||||||
if(!actionProps){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const json=JSON.stringify(actionProps);
|
|
||||||
return JSON.parse(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
const actionProps =ref(cloneProps(props.actionNode?.actionProps));
|
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if(showPanel.value!==undefined){
|
|
||||||
showPanel.value = props.drawerRight;
|
|
||||||
}
|
|
||||||
showPanel.value = props.drawerRight;
|
|
||||||
actionProps.value= cloneProps(props.actionNode?.actionProps);
|
|
||||||
});
|
|
||||||
|
|
||||||
const cancel = async() =>{
|
|
||||||
showPanel.value = false;
|
|
||||||
emit('update:drawerRight',false )
|
|
||||||
}
|
|
||||||
|
|
||||||
const save = async () =>{
|
|
||||||
showPanel.value=false;
|
|
||||||
emit('saveActionProps', actionProps.value);
|
|
||||||
emit('update:drawerRight',false );
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
cancel,
|
|
||||||
save,
|
|
||||||
actionProps,
|
|
||||||
showPanel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
</style>
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-bind="$attrs">
|
|
||||||
<q-select v-model="selectedValue" :use-chips="multiple" :label="displayName" label-color="primary"
|
|
||||||
:options="options"
|
|
||||||
stack-label
|
|
||||||
:rules="rulesExp"
|
|
||||||
:multiple="multiple"/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent,ref,watchEffect,computed } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'SelectBox',
|
|
||||||
inheritAttrs:false,
|
|
||||||
props: {
|
|
||||||
displayName:{
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
type: Array,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
selectType:{
|
|
||||||
type:String,
|
|
||||||
default:'',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: [Array,String],
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
//例:[val=>!!val ||'入力してください']
|
|
||||||
rules: {
|
|
||||||
type: String,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
required:{
|
|
||||||
type:Boolean,
|
|
||||||
default:false
|
|
||||||
},
|
|
||||||
requiredMessage: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const selectedValue = ref(props.modelValue);
|
|
||||||
const multiple = computed(()=>{
|
|
||||||
return props.selectType==='multiple'
|
|
||||||
});
|
|
||||||
watchEffect(() => {
|
|
||||||
emit('update:modelValue', selectedValue.value);
|
|
||||||
});
|
|
||||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
|
||||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
|
|
||||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
|
||||||
const rulesExp=[...requiredExp,...customExp];
|
|
||||||
return {
|
|
||||||
selectedValue,
|
|
||||||
multiple,
|
|
||||||
rulesExp
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { api } from 'boot/axios';
|
|
||||||
|
|
||||||
export class Auth
|
|
||||||
{
|
|
||||||
|
|
||||||
async login(user:string,pwd:string):Promise<boolean>
|
|
||||||
{
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
params.append('username', user);
|
|
||||||
params.append('password', pwd);
|
|
||||||
try{
|
|
||||||
const result = await api.post(`api/token`,params);
|
|
||||||
console.info(result);
|
|
||||||
localStorage.setItem('Token', result.data.access_token);
|
|
||||||
return true;
|
|
||||||
}catch(e)
|
|
||||||
{
|
|
||||||
console.info(e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import { api } from 'boot/axios';
|
|
||||||
import { ActionFlow } from 'src/types/ActionTypes';
|
|
||||||
|
|
||||||
export class FlowCtrl {
|
|
||||||
async getFlows(appId: string): Promise<ActionFlow[]> {
|
|
||||||
const flows: ActionFlow[] = [];
|
|
||||||
try {
|
|
||||||
const result = await api.get(`api/flows/${appId}`);
|
|
||||||
//console.info(result.data);
|
|
||||||
if (!result.data || !Array.isArray(result.data)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const flow of result.data) {
|
|
||||||
flows.push(ActionFlow.fromJSON(flow.content));
|
|
||||||
}
|
|
||||||
return flows;
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
return flows;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async SaveFlow(jsonData: any): Promise<boolean> {
|
|
||||||
const result = await api.post('api/flow', jsonData);
|
|
||||||
console.info(result.data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* フローを更新する
|
|
||||||
* @param jsonData
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async UpdateFlow(jsonData: any): Promise<boolean> {
|
|
||||||
const result = await api.put('api/flow/' + jsonData.flowid, jsonData);
|
|
||||||
console.info(result.data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* フローを消去する
|
|
||||||
* @param flowId
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async DeleteFlow(flowId: string): Promise<boolean> {
|
|
||||||
const result = await api.delete('api/flow/' + flowId);
|
|
||||||
console.info(result.data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* デプロイ
|
|
||||||
* @param appid
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
async depoly(appid: string): Promise<boolean> {
|
|
||||||
const result = await api.post(`api/v1/createjstokintone?app=${appid}`);
|
|
||||||
console.info(result.data);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +1 @@
|
|||||||
// app global css in SCSS form
|
// app global css in SCSS form
|
||||||
::-webkit-scrollbar {
|
|
||||||
height: 12px;
|
|
||||||
width: 14px;
|
|
||||||
background: transparent;
|
|
||||||
z-index: 12;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
width: 10px;
|
|
||||||
background-color: #c1c1c1;
|
|
||||||
border-radius: 10px;
|
|
||||||
z-index: 12;
|
|
||||||
border: 4px solid rgba(0, 0, 0, 0);
|
|
||||||
background-clip: padding-box;
|
|
||||||
transition: background-color .32s ease-in-out;
|
|
||||||
margin: 4px;
|
|
||||||
min-height: 32px;
|
|
||||||
min-width: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #c1c1c1;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,28 +2,38 @@
|
|||||||
<q-layout view="lHh Lpr lFf">
|
<q-layout view="lHh Lpr lFf">
|
||||||
<q-header elevated>
|
<q-header elevated>
|
||||||
<q-toolbar>
|
<q-toolbar>
|
||||||
<q-btn flat dense round icon="menu" aria-label="Menu" @click="toggleLeftDrawer" />
|
<q-btn
|
||||||
|
flat
|
||||||
|
dense
|
||||||
|
round
|
||||||
|
icon="menu"
|
||||||
|
aria-label="Menu"
|
||||||
|
@click="toggleLeftDrawer"
|
||||||
|
/>
|
||||||
<q-toolbar-title>
|
<q-toolbar-title>
|
||||||
{{ productName }}
|
Kintone App Builder
|
||||||
<q-badge align="top" outline>V{{ version }}</q-badge>
|
<q-badge align="top" outline>V{{ env.version }}</q-badge>
|
||||||
</q-toolbar-title>
|
</q-toolbar-title>
|
||||||
<domain-selector></domain-selector>
|
|
||||||
<q-btn flat round dense icon="logout" @click="authStore.logout()" />
|
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</q-header>
|
</q-header>
|
||||||
|
|
||||||
<q-drawer :model-value="authStore.LeftDrawer" :show-if-above="false" bordered>
|
<q-drawer
|
||||||
|
v-model="leftDrawerOpen"
|
||||||
|
:show-if-above="false"
|
||||||
|
bordered
|
||||||
|
>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item-label header>
|
<q-item-label
|
||||||
メニュー
|
header
|
||||||
|
>
|
||||||
|
Essential Links
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
|
|
||||||
<EssentialLink v-for="link in essentialLinks" :key="link.title" v-bind="link" />
|
<EssentialLink
|
||||||
<div v-if="isAdmin()">
|
v-for="link in essentialLinks"
|
||||||
<EssentialLink v-for="link in adminLinks" :key="link.title" v-bind="link" />
|
:key="link.title"
|
||||||
</div>
|
v-bind="link"
|
||||||
<EssentialLink v-for="link in domainLinks" :key="link.title" v-bind="link" />
|
/>
|
||||||
|
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
|
|
||||||
@@ -34,105 +44,105 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue';
|
import { ref } from 'vue';
|
||||||
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
|
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
|
||||||
import DomainSelector from 'components/DomainSelector.vue';
|
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
|
||||||
|
|
||||||
const essentialLinks: EssentialLinkProps[] = [
|
const essentialLinks: EssentialLinkProps[] = [
|
||||||
{
|
{
|
||||||
title: 'ホーム',
|
title: 'ホーム',
|
||||||
caption: '設計書から導入する',
|
caption: 'home',
|
||||||
icon: 'home',
|
icon: 'home',
|
||||||
link: '/',
|
link: '/',
|
||||||
target: '_self'
|
target:'_self'
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// title: 'フローエディター',
|
|
||||||
// caption: 'イベントを設定する',
|
|
||||||
// icon: 'account_tree',
|
|
||||||
// link: '/#/FlowChart',
|
|
||||||
// target: '_self'
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
title: 'アプリ管理',
|
title: 'ルールエディター',
|
||||||
caption: 'アプリを管理する',
|
caption: 'rule',
|
||||||
icon: 'widgets',
|
icon: 'rule',
|
||||||
link: '/#/app',
|
link: '/#/ruleEditor',
|
||||||
target: '_self'
|
target:'_self'
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// title: '条件エディター',
|
|
||||||
// caption: 'condition',
|
|
||||||
// icon: 'tune',
|
|
||||||
// link: '/#/condition',
|
|
||||||
// target:'_self'
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
title: '',
|
title:'',
|
||||||
isSeparator: true
|
isSeparator:true
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// title:'Kintone ポータル',
|
title:'Kintone ポータル',
|
||||||
// caption:'Kintone',
|
caption:'Kintone',
|
||||||
// icon:'cloud_queue',
|
icon:'cloud_queue',
|
||||||
// link:'https://mfu07rkgnb7c.cybozu.com/k/#/portal'
|
link:'https://mfu07rkgnb7c.cybozu.com/k/#/portal'
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// title:'CUSTOMINE',
|
title:'CUSTOMINE',
|
||||||
// caption:'gusuku',
|
caption:'gusuku',
|
||||||
// link:'https://app-customine.gusuku.io/drive.html',
|
link:'https://app-customine.gusuku.io/drive.html',
|
||||||
// icon:'settings_suggest'
|
icon:'settings_suggest'
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// title:'Kintone API ドキュメント',
|
title:'Kintone API ドキュメント',
|
||||||
// caption:'Kintone API',
|
caption:'Kintone API',
|
||||||
// link:'https://cybozu.dev/ja/kintone/docs/',
|
link:'https://cybozu.dev/ja/kintone/docs/',
|
||||||
// icon:'help_outline'
|
icon:'help_outline'
|
||||||
// },
|
},
|
||||||
|
{
|
||||||
|
title:'',
|
||||||
|
isSeparator:true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Docs',
|
||||||
|
caption: 'quasar.dev',
|
||||||
|
icon: 'school',
|
||||||
|
link: 'https://quasar.dev'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Icons',
|
||||||
|
caption: 'Material Icons',
|
||||||
|
icon: 'insert_emoticon',
|
||||||
|
link: 'https://fonts.google.com/icons?selected=Material+Icons:insert_emoticon:'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Github',
|
||||||
|
caption: 'github.com/quasarframework',
|
||||||
|
icon: 'code',
|
||||||
|
link: 'https://github.com/quasarframework'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Discord Chat Channel',
|
||||||
|
caption: 'chat.quasar.dev',
|
||||||
|
icon: 'chat',
|
||||||
|
link: 'https://chat.quasar.dev'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Forum',
|
||||||
|
caption: 'forum.quasar.dev',
|
||||||
|
icon: 'record_voice_over',
|
||||||
|
link: 'https://forum.quasar.dev'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Twitter',
|
||||||
|
caption: '@quasarframework',
|
||||||
|
icon: 'rss_feed',
|
||||||
|
link: 'https://twitter.quasar.dev'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Facebook',
|
||||||
|
caption: '@QuasarFramework',
|
||||||
|
icon: 'public',
|
||||||
|
link: 'https://facebook.quasar.dev'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Quasar Awesome',
|
||||||
|
caption: 'Community Quasar projects',
|
||||||
|
icon: 'favorite',
|
||||||
|
link: 'https://awesome.quasar.dev'
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const domainLinks: EssentialLinkProps[] = [
|
const leftDrawerOpen = ref(false)
|
||||||
{
|
|
||||||
title: 'ドメイン管理',
|
|
||||||
caption: 'kintoneのドメイン設定',
|
|
||||||
icon: 'domain',
|
|
||||||
link: '/#/domain',
|
|
||||||
target: '_self'
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// title: 'ドメイン適用',
|
|
||||||
// caption: 'ユーザー使用可能なドメインの設定',
|
|
||||||
// icon: 'assignment_ind',
|
|
||||||
// link: '/#/userDomain',
|
|
||||||
// target: '_self'
|
|
||||||
// },
|
|
||||||
];
|
|
||||||
|
|
||||||
const adminLinks: EssentialLinkProps[] = [
|
const env=process.env;
|
||||||
{
|
|
||||||
title: 'ユーザー管理',
|
|
||||||
caption: 'ユーザーを管理する',
|
|
||||||
icon: 'manage_accounts',
|
|
||||||
link: '/#/user',
|
|
||||||
target: '_self'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
const version = process.env.version;
|
|
||||||
const productName = process.env.productName;
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
authStore.toggleLeftMenu();
|
|
||||||
});
|
|
||||||
|
|
||||||
function toggleLeftDrawer() {
|
function toggleLeftDrawer() {
|
||||||
authStore.toggleLeftMenu();
|
leftDrawerOpen.value = !leftDrawerOpen.value
|
||||||
}
|
|
||||||
function isAdmin(){
|
|
||||||
const permission = authStore.permissions;
|
|
||||||
return permission === 'admin'
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user