Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99d3a01991 | |||
| ecb90e7120 | |||
| 5349c46225 | |||
| 3be4402239 | |||
| 4c482ea289 | |||
| 44a73478a7 | |||
| bceac2f172 | |||
| 98842db343 | |||
| 03904a4e35 | |||
| 09b3c8df47 | |||
| 26761f6d39 | |||
| 72608a8ffd | |||
| d1ec123c8b | |||
| 4102ff5522 | |||
| 08e857884b | |||
| c0db2d230b | |||
| 2b9b772b39 | |||
| c46e8a7047 | |||
| 6e6350d6ce | |||
| 1e7d553bd6 | |||
| 35ae2539cb | |||
|
|
a614d754f4 | ||
| 46a6ba534e | |||
| 8fecde4c42 | |||
|
|
3e73799532 | ||
| 3159366560 | |||
| 5176cff2bd | |||
| 978aa723ae | |||
| 926c338f73 | |||
| 6de60c82ba | |||
| 6ed17a50e5 | |||
| 5cd6d02f6e | |||
| 276e5e9122 | |||
| 6e75a2a524 | |||
| ea6e603036 | |||
| edad30e264 | |||
| 58616100f4 | |||
| 359558bad3 | |||
| 6a6554ed1f | |||
| e20625abdb | |||
| f83dd693d5 | |||
| a464297511 | |||
| 991c8e8083 | |||
| 9ea183ff2d | |||
| 34d368b730 | |||
| 17760a6926 | |||
| 8b9cfa34c7 | |||
| 5fb8fe53bb | |||
| 5cb9375db3 | |||
| 55181f2c57 | |||
| 4577df371a | |||
| 0f154832a5 | |||
| 5951fcc108 | |||
| 7966217ac2 | |||
| 64851bd51d | |||
| 10e584d2ac | |||
| b97a728624 | |||
| 5a875e6853 | |||
| 57a4823f61 | |||
| 761eb4c13e | |||
| 354fc6868d | |||
| 2538e4526f | |||
| 26890f5b35 | |||
| 086b5e2621 | |||
| 617b060869 | |||
| a782e92bd6 | |||
| f60f97380f | |||
| cfc416fd14 | |||
| df593d2ffe | |||
| 9cd4c8a5ab | |||
|
|
ead6658455 | ||
| f6d677b51f | |||
| 25f05ab018 | |||
| 178cf33949 | |||
| b54c0f8022 | |||
| c5cc3c1a24 | |||
| 0b414fbfbe | |||
| 981d7a5062 | |||
| 33fc0b74ef | |||
| 286acc4584 | |||
| cdfb1d4310 | |||
| 6844652b5d | |||
| 9e72acf84b | |||
| 52f4af759e | |||
| 76457b6667 | |||
| e1f2afa942 | |||
| 8d5dff60f1 | |||
| 461cd26690 | |||
| 4c6b2ea844 | |||
| 418f45f997 | |||
| 51ebe99d1c | |||
| 2f1f8a60fc | |||
| 64795a80c7 | |||
| 94a17073dd | |||
| 7f7d625fdd | |||
| 6902079866 | |||
| 6aa057e590 | |||
| 2721cd60d1 | |||
| 1f8d079d4d | |||
|
|
f34dec1054 | ||
|
|
01b64f1aba | ||
|
|
3367ada343 | ||
|
|
f4ea3eaccb | ||
|
|
4adb8401d6 | ||
| df59bff6ae | |||
| 3ae685a0e2 | |||
|
|
fce56e43c3 | ||
|
|
42618602f4 | ||
| c1e50736e8 | |||
|
|
e02131846b | ||
|
|
2c3b27d9de | ||
|
|
6ccc833f7d | ||
|
|
a0ecc2eee3 | ||
| 59e6d33656 | |||
| b641c729c2 | |||
| 142cdcda38 | |||
| fc2669dabf | |||
| 8e095b51e3 | |||
| ff03490209 | |||
| 40cd9998d0 | |||
| 973ba159b4 | |||
| 063a5af822 | |||
|
|
6a06c71104 | ||
| cccff1d16d | |||
|
|
100d8de54f | ||
|
|
7c667660c0 | ||
|
|
4eb56372a5 | ||
|
|
16edd398be | ||
|
|
4e08159e6d | ||
| 7a9718a6fa | |||
| f597f7aa5a | |||
| 0ec2b22754 | |||
| a04f7b1bd5 | |||
| 2240603c2c | |||
| d9a7532805 | |||
| e59f9b802b | |||
| ad1c330231 | |||
| a1905a1274 | |||
| e515f99a44 | |||
| d42fac9a7d | |||
| da3df6f0a7 | |||
| 0bf3a1b2c8 | |||
| b63999c7f9 | |||
| 772ab3c6a5 | |||
| e3c66a5bc4 | |||
| 9e510b0183 |
4
backend/.gitignore
vendored
4
backend/.gitignore
vendored
@@ -56,6 +56,7 @@ 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
|
||||||
@@ -125,4 +126,5 @@ cython_debug/
|
|||||||
# VS Code settings
|
# VS Code settings
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
*.lock
|
*.lock
|
||||||
|
Temp/
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
FROM python:3.8
|
FROM python:3.11.4
|
||||||
|
|
||||||
RUN mkdir /app
|
RUN mkdir /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
38
backend/Temp/alc_runtime.js
Normal file
38
backend/Temp/alc_runtime.js
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -29,18 +29,19 @@ async def login(
|
|||||||
else:
|
else:
|
||||||
permissions = "user"
|
permissions = "user"
|
||||||
access_token = security.create_access_token(
|
access_token = security.create_access_token(
|
||||||
data={"sub": user.email, "permissions": permissions},
|
data={"sub": user.id, "permissions": permissions},
|
||||||
expires_delta=access_token_expires,
|
expires_delta=access_token_expires,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"access_token": access_token, "token_type": "bearer"}
|
return {"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
|
||||||
|
|
||||||
|
|
||||||
@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)
|
user = sign_up_new_user(db, form_data.username, form_data.password,firstname,lastname)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT,
|
status_code=status.HTTP_409_CONFLICT,
|
||||||
@@ -56,8 +57,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.email, "permissions": permissions},
|
data={"sub": user.id, "permissions": permissions},
|
||||||
expires_delta=access_token_expires,
|
expires_delta=access_token_expires,
|
||||||
)
|
)
|
||||||
|
|
||||||
return {"access_token": access_token, "token_type": "bearer"}
|
return {"access_token": access_token, "token_type": "bearer","user_name":user.first_name + " " + user.last_name}
|
||||||
|
|||||||
@@ -5,33 +5,150 @@ import pandas as pd
|
|||||||
import json
|
import json
|
||||||
import httpx
|
import httpx
|
||||||
import deepdiff
|
import deepdiff
|
||||||
import app.core.config as c
|
import app.core.config as config
|
||||||
|
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 getfieldsfromexcel(df):
|
def getkintoneenv(user = Depends(get_current_user)):
|
||||||
|
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(5,len(df)):
|
for row in range(startrow,len(df)):
|
||||||
if pd.isna(df.iloc[row,1]):
|
if pd.isna(df.iloc[row,startcolumn]):
|
||||||
break
|
break
|
||||||
if not df.iloc[row,3] in c.KINTONE_FIELD_TYPE:
|
if not df.iloc[row,typecolumn] in config.KINTONE_FIELD_TYPE:
|
||||||
continue
|
continue
|
||||||
p=[]
|
p=[]
|
||||||
for column in range(1,7):
|
for column in range(startcolumn,startcolumn + len(property)):
|
||||||
if(not pd.isna(df.iloc[row,column])):
|
if(not pd.isna(df.iloc[row,column])):
|
||||||
if(property[column-1]=="options"):
|
propertyname =property[column-1]
|
||||||
o=[]
|
if(propertyname.find("[") == 0):
|
||||||
for v in df.iloc[row,column].split(','):
|
continue
|
||||||
o.append(f"\"{v.split('|')[0]}\":{{\"label\":\"{v.split('|')[0]}\",\"index\":\"{v.split('|')[1]}\"}}")
|
elif (propertyname =="remark"):
|
||||||
p.append(f"\"{property[column-1]}\":{{{','.join(o)}}}")
|
if (df.iloc[row,column].find("|") !=-1):
|
||||||
elif(property[column-1]=="required"):
|
propertyname = "options"
|
||||||
p.append(f"\"{property[column-1]}\":{df.iloc[row,column]}")
|
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
||||||
|
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:
|
||||||
|
continue
|
||||||
|
elif(propertyname =="mixValue"):
|
||||||
|
if(df.iloc[row,column].find("レコード登録時の日") != -1):
|
||||||
|
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:
|
else:
|
||||||
p.append(f"\"{property[column-1]}\":\"{df.iloc[row,column]}\"")
|
p = p + createkintonefields(propertyname, df.iloc[row,column],trueformat)
|
||||||
col.append(f"\"{df.iloc[row,2]}\":{{{','.join(p)}}}")
|
|
||||||
fields = ",".join(col).replace("False","false").replace("True","true")
|
# 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):
|
||||||
@@ -39,10 +156,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):
|
def getsettingfromkintone(app:str,c:config.KINTONE_ENV):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
params = {"app":app}
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/app/settings.json"
|
url = f"{c.BASE_URL}{config.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()
|
||||||
|
|
||||||
@@ -54,24 +171,24 @@ def analysesettings(excel,kintone):
|
|||||||
updatesettings[key] = excel[key]
|
updatesettings[key] = excel[key]
|
||||||
return updatesettings
|
return updatesettings
|
||||||
|
|
||||||
def createkintoneapp(name:str):
|
def createkintoneapp(name:str,c:config.KINTONE_ENV):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
data = {"name":name}
|
data = {"name":name}
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app.json"
|
url = f"{c.BASE_URL}{config.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):
|
def updateappsettingstokintone(app:str,updates:dict,c:config.KINTONE_ENV):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/settings.json"
|
url = f"{c.BASE_URL}{config.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,revision:str = None):
|
def addfieldstokintone(app:str,fields:dict,c:config.KINTONE_ENV,revision:str = None):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/form/fields.json"
|
url = f"{c.BASE_URL}{config.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:
|
||||||
@@ -79,32 +196,32 @@ def addfieldstokintone(app:str,fields:dict,revision:str = None):
|
|||||||
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 updatefieldstokintone(app:str,revision:str,fields:dict):
|
def updatefieldstokintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/form/fields.json"
|
url = f"{c.BASE_URL}{config.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):
|
def deletefieldsfromkintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/form/fields.json"
|
url = f"{c.BASE_URL}{config.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):
|
def deoployappfromkintone(app:str,revision:str,c:config.KINTONE_ENV):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/deploy.json"
|
url = f"{c.BASE_URL}{config.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):
|
def getfieldsfromkintone(app:str,c:config.KINTONE_ENV):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
params = {"app":app}
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/app/form/fields.json"
|
url = f"{c.BASE_URL}{config.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()
|
||||||
|
|
||||||
@@ -116,22 +233,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 property:
|
for p in config.KINTONE_FIELD_PROPERTY:
|
||||||
if excel[key].get(p) != None and kintone[key][p] != excel[key][p]:
|
if excel[key].get(p) != None and kintone[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 c.KINTONE_FIELD_TYPE:
|
if kintone[key]["type"] in config.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):
|
def getprocessfromkintone(app:str,c:config.KINTONE_ENV):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
params = {"app":app}
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/app/status.json"
|
url = f"{c.BASE_URL}{config.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()
|
||||||
|
|
||||||
@@ -195,27 +312,75 @@ def analysprocess(excel,kintone):
|
|||||||
# return True
|
# return True
|
||||||
return diff
|
return diff
|
||||||
|
|
||||||
def updateprocesstokintone(app:str,process:dict):
|
def updateprocesstokintone(app:str,process:dict,c:config.KINTONE_ENV):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/status.json"
|
url = f"{c.BASE_URL}{config.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():
|
def getkintoneusers(c:config.KINTONE_ENV):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={config.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():
|
def getkintoneorgs(c:config.KINTONE_ENV):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={config.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,c: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:c.API_V1_AUTH_VALUE}
|
||||||
|
data ={'name':'file','filename':os.path.basename(file)}
|
||||||
|
url = f"{c.BASE_URL}/k/v1/file.json"
|
||||||
|
r = httpx.post(url,headers=headers,data=data,files=upload_files)
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
def updateappjscss(app,uploads,c:config.KINTONE_ENV):
|
||||||
|
dsjs = []
|
||||||
|
dscss = []
|
||||||
|
for upload in uploads:
|
||||||
|
for key in upload:
|
||||||
|
if key.endswith('.js'):
|
||||||
|
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'):
|
||||||
|
dscss.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||||
|
ds ={'js':dsjs,'css':dscss}
|
||||||
|
mb ={'js':[],'css':[]}
|
||||||
|
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb}
|
||||||
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
|
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||||
|
print(data)
|
||||||
|
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
def createappjs(domainid,app):
|
||||||
|
db = SessionLocal()
|
||||||
|
flows = get_flows_by_app(db,domainid,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")
|
||||||
|
print(rootdir)
|
||||||
|
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'):
|
||||||
@@ -233,15 +398,26 @@ 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}: {str(e)}")
|
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}")
|
||||||
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("/upload",)
|
@r.post("/download",)
|
||||||
async def upload(files:t.List[UploadFile] = File(...)):
|
async def download(request:Request,key,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||||
|
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'):
|
||||||
@@ -251,61 +427,103 @@ async def upload(files:t.List[UploadFile] = File(...)):
|
|||||||
print(df)
|
print(df)
|
||||||
dataframes.append(df)
|
dataframes.append(df)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}")
|
raise APIException('kintone:upload',request.url._url,f"Error occurred while uploading file {file.filename}:",e)
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
|
raise APIException('kintone:upload',request.url._url, f"File {file.filename} is not an Excel file",e)
|
||||||
|
|
||||||
return {"files": [file.filename for file in files]}
|
return {"files": [file.filename for file in files]}
|
||||||
|
|
||||||
@r.get("/allapps",)
|
@r.post("/updatejscss")
|
||||||
async def allapps():
|
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
try:
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/apps.json"
|
jscs=[]
|
||||||
r = httpx.get(url,headers=headers)
|
for file in files:
|
||||||
return r.json()
|
fbytes = file.file.read()
|
||||||
|
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(app:str):
|
async def app(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
try:
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/app.json"
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
params ={"id":app}
|
url = f"{c.BASE_URL}{config.API_V1_STR}/app.json"
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
params ={"id":app}
|
||||||
return r.json()
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
|
return r.json()
|
||||||
|
except Exception as e:
|
||||||
|
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({c.DOMAIN_NAM}->{app}):",e)
|
||||||
|
|
||||||
|
@r.get("/allapps")
|
||||||
|
async def allapps(request:Request,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||||
|
try:
|
||||||
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
|
url = f"{c.BASE_URL}{config.API_V1_STR}/apps.json"
|
||||||
|
r = httpx.get(url,headers=headers)
|
||||||
|
return r.json()
|
||||||
|
except Exception as e:
|
||||||
|
raise APIException('kintone:allapps',request.url._url, f"Error occurred while get allapps({c.DOMAIN_NAM}):",e)
|
||||||
|
|
||||||
@r.get("/appfields")
|
@r.get("/appfields")
|
||||||
async def appfields(app:str):
|
async def appfields(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||||
return getfieldsfromkintone(app)
|
try:
|
||||||
|
return getfieldsfromkintone(app,env)
|
||||||
|
except Exception as e:
|
||||||
|
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAM}->{app}):",e)
|
||||||
|
|
||||||
@r.get("/appprocess")
|
@r.get("/appprocess")
|
||||||
async def appprocess(app:str):
|
async def appprocess(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||||
return getprocessfromkintone(app)
|
try:
|
||||||
|
return getprocessfromkintone(app,env)
|
||||||
|
except Exception as e:
|
||||||
|
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAM}->{app}):",e)
|
||||||
|
|
||||||
@r.get("/alljscs")
|
@r.get("/alljscss")
|
||||||
async def alljscs(app:str):
|
async def alljscs(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
try:
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/app/customize.json"
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
url = f"{c.BASE_URL}{config.API_V1_STR}/app/customize.json"
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
params = {"app":app}
|
||||||
return r.json()
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
|
return r.json()
|
||||||
|
except Exception as e:
|
||||||
|
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({c.DOMAIN_NAM}->{app}):",e)
|
||||||
|
|
||||||
@r.post("/createapp",)
|
@r.post("/createapp",)
|
||||||
async def createapp(name:str):
|
async def createapp(request:Request,name:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||||
headers={c.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
try:
|
||||||
data = {"name":name}
|
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app.json"
|
data = {"name":name}
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||||
result = r.json()
|
|
||||||
if result.get("app") != None:
|
|
||||||
url = f"{c.BASE_URL}{c.API_V1_STR}/preview/app/deploy.json"
|
|
||||||
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
|
result = r.json()
|
||||||
|
if result.get("app") != None:
|
||||||
|
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||||
|
data = {"apps":[result],"revert": False}
|
||||||
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
|
return r.json
|
||||||
|
except Exception as e:
|
||||||
|
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({c.DOMAIN_NAM}->{name}):",e)
|
||||||
|
|
||||||
property=["label","code","type","required","defaultValue","options"]
|
|
||||||
|
|
||||||
@r.post("/createappfromexcel",)
|
@r.post("/createappfromexcel",)
|
||||||
async def createappfromexcel(files:t.List[UploadFile] = File(...)):
|
async def createappfromexcel(request:Request,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
|
||||||
|
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:
|
||||||
@@ -315,87 +533,90 @@ async def createappfromexcel(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)
|
fields = getfieldsfromexcel(df,mapping)
|
||||||
users = getkintoneusers()
|
users = getkintoneusers(env)
|
||||||
orgs = getkintoneorgs()
|
orgs = getkintoneorgs(env)
|
||||||
processes = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
|
processes = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
|
||||||
app = createkintoneapp(appname)
|
app = createkintoneapp(appname,env)
|
||||||
if app.get("app") != None:
|
if app.get("app") != None:
|
||||||
result["app"] = app["app"]
|
result["app"] = app["app"]
|
||||||
app = updateappsettingstokintone(result["app"],{"description":desc})
|
app = updateappsettingstokintone(result["app"],{"description":desc},env)
|
||||||
if app.get("revision") != None:
|
if app.get("revision") != None:
|
||||||
result["revision"] = app["revision"]
|
result["revision"] = app["revision"]
|
||||||
app = addfieldstokintone(result["app"],fields)
|
app = addfieldstokintone(result["app"],fields,env)
|
||||||
if len(processes)> 0:
|
if len(processes)> 0:
|
||||||
app = updateprocesstokintone(result["app"],processes)
|
app = updateprocesstokintone(result["app"],processes,env)
|
||||||
if app.get("revision") != None:
|
if app.get("revision") != None:
|
||||||
result["revision"] = app["revision"]
|
result["revision"] = app["revision"]
|
||||||
deoployappfromkintone(result["app"],result["revision"])
|
deoployappfromkintone(result["app"],result["revision"],env)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}")
|
raise APIException('kintone:createappfromexcel',request.url._url, f"Error occurred while parsing file ({env.DOMAIN_NAME}->{file.filename}):",e)
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
|
raise APIException('kintone:createappfromexcel',request.url._url, f"File {file.filename} is not an Excel file",e)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@r.post("/updateappfromexcel",)
|
@r.post("/updateappfromexcel")
|
||||||
async def updateappfromexcel(app:str,files:t.List[UploadFile] = File(...)):
|
async def updateappfromexcel(request:Request,app:str,files:t.List[UploadFile] = File(...),format:int = 0,env = Depends(getkintoneenv)):
|
||||||
|
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)
|
kintone= getsettingfromkintone(app,env)
|
||||||
settings = analysesettings(excel,kintone)
|
settings = analysesettings(excel,kintone)
|
||||||
excel = getfieldsfromexcel(df)
|
excel = getfieldsfromexcel(df,mapping)
|
||||||
kintone = getfieldsfromkintone(app)
|
kintone = getfieldsfromkintone(app,env)
|
||||||
users = getkintoneusers()
|
users = getkintoneusers(env)
|
||||||
orgs = getkintoneorgs()
|
orgs = getkintoneorgs(env)
|
||||||
exp = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
|
exp = getprocessfromexcel(df,users["users"], orgs["organizationTitles"])
|
||||||
#exp = getprocessfromexcel(df)
|
#exp = getprocessfromexcel(df)
|
||||||
kinp = getprocessfromkintone(app)
|
kinp = getprocessfromkintone(app,env)
|
||||||
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"])
|
result = updatefieldstokintone(app,revision,fields["update"],env)
|
||||||
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"],revision)
|
result = addfieldstokintone(app,fields["add"],env,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"])
|
result = deletefieldsfromkintone(app,revision,fields["del"],env)
|
||||||
revision = result["revision"]
|
revision = result["revision"]
|
||||||
deploy = True
|
deploy = True
|
||||||
if len(settings) > 0:
|
if len(settings) > 0:
|
||||||
result = updateappsettingstokintone(app,settings)
|
result = updateappsettingstokintone(app,settings,env)
|
||||||
revision = result["revision"]
|
revision = result["revision"]
|
||||||
deploy = True
|
deploy = True
|
||||||
if len(process)>0:
|
if len(process)>0:
|
||||||
result = updateprocesstokintone(app,exp)
|
result = updateprocesstokintone(app,exp,env)
|
||||||
revision = result["revision"]
|
revision = result["revision"]
|
||||||
deploy = True
|
deploy = True
|
||||||
if deploy:
|
if deploy:
|
||||||
result = deoployappfromkintone(app,revision)
|
result = deoployappfromkintone(app,revision,env)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=f"Error occurred while parsing file {file.filename}: {str(e)}")
|
raise APIException('kintone:updateappfromexcel',request.url._url, f"Error occurred while parsing file ({env.DOMAIN_NAME}->{file.filename}):",e)
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=400, detail=f"File {file.filename} is not an Excel file")
|
raise APIException('kintone:updateappfromexcel',request.url._url, f"File {file.filename} is not an Excel file",e)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@r.post("/updateprocessfromexcel",)
|
@r.post("/updateprocessfromexcel",)
|
||||||
async def updateprocessfromexcel(app:str):
|
async def updateprocessfromexcel(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
excel = getprocessfromexcel()
|
excel = getprocessfromexcel()
|
||||||
kintone = getprocessfromkintone(app)
|
kintone = getprocessfromkintone(app,env)
|
||||||
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"}
|
||||||
@@ -416,14 +637,31 @@ async def updateprocessfromexcel(app:str):
|
|||||||
# result = updateappsettingstokintone(app,settings)
|
# result = updateappsettingstokintone(app,settings)
|
||||||
# revision = result["revision"]
|
# revision = result["revision"]
|
||||||
# deploy = True
|
# deploy = True
|
||||||
result = updateprocesstokintone(app,excel)
|
result = updateprocesstokintone(app,excel,env)
|
||||||
revision = result["revision"]
|
revision = result["revision"]
|
||||||
deploy = True
|
deploy = True
|
||||||
if deploy:
|
if deploy:
|
||||||
result = deoployappfromkintone(app,revision)
|
result = deoployappfromkintone(app,revision,env)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=f"Error occurred : {str(e)}")
|
raise APIException('kintone:updateprocessfromexcel',request.url._url, f"Error occurred while update process ({env.DOMAIN_NAM}->{app}):",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.DOMAIN_ID, app))
|
||||||
|
files.append('Temp\\alc_runtime.js')
|
||||||
|
for file in files:
|
||||||
|
upload = uploadkintonefiles(file,env)
|
||||||
|
if upload.get('fileKey') != None:
|
||||||
|
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_NAM}->{app}):",e)
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ from fastapi import Request,Depends, APIRouter, UploadFile,HTTPException,File
|
|||||||
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 AppBase, AppEdit, App,Kintone
|
from app.db.schemas import *
|
||||||
|
from typing import List
|
||||||
|
from app.core.auth import get_current_active_user,get_current_user
|
||||||
|
from app.core.apiexception import APIException
|
||||||
|
|
||||||
platform_router = r = APIRouter()
|
platform_router = r = APIRouter()
|
||||||
|
|
||||||
@@ -16,9 +19,11 @@ async def appsetting_details(
|
|||||||
id: int,
|
id: int,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
app = get_appsetting(db, id)
|
try:
|
||||||
return app
|
app = get_appsetting(db, id)
|
||||||
|
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(
|
||||||
@@ -26,7 +31,10 @@ async def appsetting_create(
|
|||||||
app: AppBase,
|
app: AppBase,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
return create_appsetting(db, app)
|
try:
|
||||||
|
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(
|
||||||
@@ -38,7 +46,10 @@ async def appsetting_edit(
|
|||||||
app: AppEdit,
|
app: AppEdit,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
return edit_appsetting(db, id, app)
|
try:
|
||||||
|
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(
|
||||||
@@ -49,8 +60,10 @@ 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(
|
||||||
@@ -63,5 +76,270 @@ async def kintone_data(
|
|||||||
type: int,
|
type: int,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
kintone = get_kintones(db, type)
|
try:
|
||||||
return kintone
|
kintone = get_kintones(db, type)
|
||||||
|
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_user),
|
||||||
|
db=Depends(get_db),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
domain = get_activedomain(db, user.id)
|
||||||
|
print("domain=>",domain)
|
||||||
|
flows = get_flows_by_app(db, domain.id, 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: FlowBase,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
db=Depends(get_db),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
domain = get_activedomain(db, user.id)
|
||||||
|
return create_flow(db, domain.id, 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,
|
||||||
|
flow: FlowBase,
|
||||||
|
db=Depends(get_db),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return edit_flow(db, flow)
|
||||||
|
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,
|
||||||
|
db=Depends(get_db),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return create_domain(db, domain)
|
||||||
|
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,
|
||||||
|
db=Depends(get_db),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return edit_domain(db, domain)
|
||||||
|
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,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
db=Depends(get_db),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
domains = get_domain(db, 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,
|
||||||
|
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,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
db=Depends(get_db),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
domain = get_activedomain(db, user.id)
|
||||||
|
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,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
db=Depends(get_db),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
domain = active_userdomain(db, 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)
|
||||||
26
backend/app/core/apiexception.py
Normal file
26
backend/app/core/apiexception.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from fastapi import HTTPException, status
|
||||||
|
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):
|
||||||
|
if(str(e) == ''):
|
||||||
|
content += e.detail
|
||||||
|
self.detail = e.detail
|
||||||
|
self.status_code = e.status_code
|
||||||
|
else:
|
||||||
|
self.detail = str(e)
|
||||||
|
content += str(e)
|
||||||
|
self.status_code = 500
|
||||||
|
if(len(content) > 5000):
|
||||||
|
content =content[0:5000]
|
||||||
|
self.error = ErrorCreate(location=location,title=title,content=content)
|
||||||
|
|
||||||
|
def writedblog(exc: APIException):
|
||||||
|
db = SessionLocal()
|
||||||
|
try:
|
||||||
|
create_log(db,exc.error)
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
@@ -3,7 +3,7 @@ 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
|
from app.db.crud import get_user_by_email, create_user,get_user
|
||||||
from app.core import security
|
from app.core import security
|
||||||
|
|
||||||
|
|
||||||
@@ -19,14 +19,14 @@ async def get_current_user(
|
|||||||
payload = jwt.decode(
|
payload = jwt.decode(
|
||||||
token, security.SECRET_KEY, algorithms=[security.ALGORITHM]
|
token, security.SECRET_KEY, algorithms=[security.ALGORITHM]
|
||||||
)
|
)
|
||||||
email: str = payload.get("sub")
|
id: int = payload.get("sub")
|
||||||
if email is None:
|
if id is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
permissions: str = payload.get("permissions")
|
permissions: str = payload.get("permissions")
|
||||||
token_data = schemas.TokenData(email=email, permissions=permissions)
|
token_data = schemas.TokenData(id = id, permissions=permissions)
|
||||||
except PyJWTError:
|
except PyJWTError:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
user = get_user_by_email(db, token_data.email)
|
user = get_user(db, token_data.id)
|
||||||
if user is None:
|
if user is None:
|
||||||
raise credentials_exception
|
raise credentials_exception
|
||||||
return user
|
return user
|
||||||
@@ -58,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):
|
def sign_up_new_user(db, email: str, password: str, firstname: str,lastname: 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
|
||||||
@@ -67,6 +67,8 @@ def sign_up_new_user(db, email: str, password: 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,18 +1,39 @@
|
|||||||
import os
|
import os
|
||||||
|
import base64
|
||||||
|
|
||||||
PROJECT_NAME = "KintoneAppBuilder"
|
PROJECT_NAME = "KintoneAppBuilder"
|
||||||
|
|
||||||
SQLALCHEMY_DATABASE_URI = "mssql+pymssql://maxz64@maxzdb:m@xz1205@maxzdb.database.windows.net/alloc"
|
#SQLALCHEMY_DATABASE_URI = "postgres://maxz64:m@xz1205@alicornkintone.postgres.database.azure.com/postgres"
|
||||||
|
SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/postgres"
|
||||||
BASE_URL = "https://mfu07rkgnb7c.cybozu.com"
|
|
||||||
|
|
||||||
API_V1_STR = "/k/v1"
|
API_V1_STR = "/k/v1"
|
||||||
|
|
||||||
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
||||||
|
|
||||||
API_V1_AUTH_VALUE = "TVhaOm1heHoxMjA1"
|
DEPLOY_MODE = "DEV" #DEV,PROD
|
||||||
|
|
||||||
KINTONE_USER = "MXZ"
|
DEPLOY_JS_URL = "https://ka-addin.azurewebsites.net/alc_runtime.js"
|
||||||
|
#DEPLOY_JS_URL = "https://e84c-133-139-70-142.ngrok-free.app/alc_runtime.js"
|
||||||
|
|
||||||
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']
|
||||||
|
|
||||||
|
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.kintonepwd}","utf-8"))
|
||||||
@@ -25,7 +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=15)
|
expire = datetime.utcnow() + timedelta(minutes=2880)
|
||||||
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
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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
|
||||||
@@ -115,4 +116,183 @@ def get_kintones(db: Session, type: int):
|
|||||||
kintones = db.query(models.Kintone).filter(models.Kintone.type == type).all()
|
kintones = db.query(models.Kintone).filter(models.Kintone.type == type).all()
|
||||||
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, domainid: int, flow: schemas.FlowBase):
|
||||||
|
db_flow = models.Flow(
|
||||||
|
flowid=flow.flowid,
|
||||||
|
appid=flow.appid,
|
||||||
|
eventid=flow.eventid,
|
||||||
|
domainid=domainid,
|
||||||
|
name=flow.name,
|
||||||
|
content=flow.content
|
||||||
|
)
|
||||||
|
db.add(db_flow)
|
||||||
|
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, flow: schemas.FlowBase
|
||||||
|
) -> schemas.Flow:
|
||||||
|
db_flow = get_flow(db, flow.flowid)
|
||||||
|
if not db_flow:
|
||||||
|
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
|
||||||
|
update_data = flow.dict(exclude_unset=True)
|
||||||
|
|
||||||
|
for key, value in update_data.items():
|
||||||
|
setattr(db_flow, key, value)
|
||||||
|
|
||||||
|
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, domainid: int, appid: str):
|
||||||
|
flows = db.query(models.Flow).filter(and_(models.Flow.domainid == domainid,models.Flow.appid == appid)).all()
|
||||||
|
if not flows:
|
||||||
|
raise Exception("Data not found")
|
||||||
|
return flows
|
||||||
|
|
||||||
|
def create_domain(db: Session, domain: schemas.DomainBase):
|
||||||
|
db_domain = models.Domain(
|
||||||
|
tenantid = domain.tenantid,
|
||||||
|
name=domain.name,
|
||||||
|
url=domain.url,
|
||||||
|
kintoneuser=domain.kintoneuser,
|
||||||
|
kintonepwd=domain.kintonepwd
|
||||||
|
)
|
||||||
|
db.add(db_domain)
|
||||||
|
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
|
||||||
|
) -> schemas.Domain:
|
||||||
|
db_domain = db.query(models.Domain).get(domain.id)
|
||||||
|
if not db_domain:
|
||||||
|
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||||
|
update_data = domain.dict(exclude_unset=True)
|
||||||
|
|
||||||
|
for key, value in update_data.items():
|
||||||
|
if(key != "id"):
|
||||||
|
setattr(db_domain, key, value)
|
||||||
|
|
||||||
|
db.add(db_domain)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_domain)
|
||||||
|
return db_domain
|
||||||
|
|
||||||
|
def add_userdomain(db: Session, userid:int,domainids:list):
|
||||||
|
for domainid in domainids:
|
||||||
|
db_domain = models.UserDomain(
|
||||||
|
userid = userid,
|
||||||
|
domainid = domainid
|
||||||
|
)
|
||||||
|
db.add(db_domain)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_domain)
|
||||||
|
return db_domain
|
||||||
|
|
||||||
|
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):
|
||||||
|
db_domain = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(and_(models.UserDomain.userid == userid,models.UserDomain.active == True)).first()
|
||||||
|
if not db_domain:
|
||||||
|
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")
|
||||||
|
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")
|
||||||
|
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_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()
|
||||||
|
eveactions = db.query(models.Action).join(models.EventAction,models.EventAction.actionid != models.Action.id and models.EventAction.eventid == eventid ).join(models.Event,models.Event.id == models.EventAction.eventid).filter(models.Event.eventid == eventid).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,12 +1,16 @@
|
|||||||
from sqlalchemy import Boolean, Column, Integer, String
|
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey
|
||||||
|
from sqlalchemy.ext.declarative import as_declarative
|
||||||
from .session import Base
|
from datetime import datetime
|
||||||
|
|
||||||
|
@as_declarative()
|
||||||
|
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)
|
||||||
|
|
||||||
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))
|
||||||
@@ -17,15 +21,93 @@ class User(Base):
|
|||||||
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(500))
|
desc = Column(String)
|
||||||
content = Column(String(2000))
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
domainid = Column(Integer,ForeignKey("domain.id"))
|
||||||
|
name = Column(String(200))
|
||||||
|
content = Column(String)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
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(Integer,ForeignKey("event.id"))
|
||||||
|
actionid = Column(Integer,ForeignKey("action.id"))
|
||||||
|
|
||||||
|
|
||||||
|
class ErrorLog(Base):
|
||||||
|
__tablename__ = "errorlog"
|
||||||
|
|
||||||
|
title = Column(String(50))
|
||||||
|
location = Column(String(500))
|
||||||
|
content = Column(String(5000))
|
||||||
|
|
||||||
|
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))
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from datetime import datetime
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
|
|
||||||
|
class Base(BaseModel):
|
||||||
|
create_time: datetime
|
||||||
|
update_time: datetime
|
||||||
|
|
||||||
class UserBase(BaseModel):
|
class UserBase(BaseModel):
|
||||||
email: str
|
email: str
|
||||||
is_active: bool = True
|
is_active: bool = True
|
||||||
@@ -15,7 +20,12 @@ class UserOut(UserBase):
|
|||||||
|
|
||||||
|
|
||||||
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 Config:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
@@ -41,6 +51,7 @@ class Token(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class TokenData(BaseModel):
|
class TokenData(BaseModel):
|
||||||
|
id:int = 0
|
||||||
email: str = None
|
email: str = None
|
||||||
permissions: str = "user"
|
permissions: str = "user"
|
||||||
|
|
||||||
@@ -68,4 +79,70 @@ class Kintone(BaseModel):
|
|||||||
content: str = None
|
content: str = None
|
||||||
|
|
||||||
class Config:
|
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
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
class FlowBase(BaseModel):
|
||||||
|
flowid: str
|
||||||
|
appid: str
|
||||||
|
eventid: str
|
||||||
|
name: str = None
|
||||||
|
content: str = None
|
||||||
|
|
||||||
|
class Flow(Base):
|
||||||
|
id: int
|
||||||
|
flowid: str
|
||||||
|
appid: str
|
||||||
|
eventid: str
|
||||||
|
domainid: int
|
||||||
|
name: str = None
|
||||||
|
content: str = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
class DomainBase(BaseModel):
|
||||||
|
id: int
|
||||||
|
tenantid: str
|
||||||
|
name: str
|
||||||
|
url: str
|
||||||
|
kintoneuser: str
|
||||||
|
kintonepwd: str
|
||||||
|
|
||||||
|
class Domain(Base):
|
||||||
|
id: int
|
||||||
|
tenantid: str
|
||||||
|
name: str
|
||||||
|
url: str
|
||||||
|
kintoneuser: str
|
||||||
|
kintonepwd: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
class Event(Base):
|
||||||
|
id: int
|
||||||
|
category: str
|
||||||
|
type: str
|
||||||
|
eventid: str
|
||||||
|
function: str
|
||||||
|
mobile: bool
|
||||||
|
eventgroup: bool
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
class ErrorCreate(BaseModel):
|
||||||
|
title:str
|
||||||
|
location:str
|
||||||
|
content:str
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
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
|
||||||
@@ -10,7 +11,12 @@ from app.db import Base,engine
|
|||||||
from app.core.auth import get_current_active_user
|
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
|
||||||
|
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)
|
||||||
|
|
||||||
@@ -18,6 +24,17 @@ app = FastAPI(
|
|||||||
title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api"
|
title=config.PROJECT_NAME, docs_url="/api/docs", openapi_url="/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
origins = [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=origins,
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
# @app.middleware("http")
|
# @app.middleware("http")
|
||||||
# async def db_session_middleware(request: Request, call_next):
|
# async def db_session_middleware(request: Request, call_next):
|
||||||
@@ -26,6 +43,25 @@ app = FastAPI(
|
|||||||
# 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():
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ pyjwt==1.7.1
|
|||||||
pandas==2.0.3
|
pandas==2.0.3
|
||||||
openpyxl==3.1.2
|
openpyxl==3.1.2
|
||||||
deepdiff==6.3.1
|
deepdiff==6.3.1
|
||||||
pymssql==2.2.7
|
pymssql==2.2.7
|
||||||
|
psycopg2==2.9.8
|
||||||
845
document/ALCKintone_20231012.drawio
Normal file
845
document/ALCKintone_20231012.drawio
Normal file
@@ -0,0 +1,845 @@
|
|||||||
|
<mxfile host="65bd71144e" pages="3">
|
||||||
|
<diagram name="Page-1" id="efa7a0a1-bf9b-a30e-e6df-94a7791c09e9">
|
||||||
|
<mxGraphModel dx="1073" dy="518" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="826" pageHeight="1169" background="none" math="0" shadow="0">
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-114" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><strong>User</strong></p><p style="margin: 0px; margin-left: 8px;"><br></p><p style="margin: 0px; margin-left: 8px;"><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="21.64" width="160" height="78.36" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-116" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" target="ZIlfFuTIaODnUzWRKW0w-114" edge="1">
|
||||||
|
<mxGeometry x="389.35999999999996" y="350" as="geometry">
|
||||||
|
<mxPoint x="350" y="60.820000000000164" as="sourcePoint"/>
|
||||||
|
<mxPoint x="671" y="532" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-117" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-116" connectable="0" vertex="1">
|
||||||
|
<mxGeometry x="-1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="-30" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-118" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-116" connectable="0" vertex="1">
|
||||||
|
<mxGeometry x="1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="18.919999999999582" y="-21.960000000000644" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-119" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><strong>UserApp</strong></p><hr><p style="margin: 0px; margin-left: 8px;"><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="350" y="250" width="160" height="78.36" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-120" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;entryX=0.5;entryY=1;entryDx=0;entryDy=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;" parent="1" source="ZIlfFuTIaODnUzWRKW0w-119" edge="1">
|
||||||
|
<mxGeometry x="399.35999999999996" y="360" as="geometry">
|
||||||
|
<mxPoint x="360" y="71" as="sourcePoint"/>
|
||||||
|
<mxPoint x="430" y="100.00000000000023" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="430" y="140"/>
|
||||||
|
<mxPoint x="430" y="140"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-121" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-120" connectable="0" vertex="1">
|
||||||
|
<mxGeometry x="-1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="9" y="-20" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-122" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-120" connectable="0" vertex="1">
|
||||||
|
<mxGeometry x="1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="18.919999999999582" y="-21.960000000000644" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-123" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><strong>UserDomain</strong></p><hr><p style="margin: 0px; margin-left: 8px;"><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="350" y="21.64" width="160" height="78.36" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-124" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><b>Action</b></p><hr><p style="margin: 0px; margin-left: 8px;"><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="30" y="440" width="160" height="78.36" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-125" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><strong>AppAction</strong></p><hr><p style="margin: 0px; margin-left: 8px;"><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="350" y="440" width="160" height="78.36" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-130" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="ZIlfFuTIaODnUzWRKW0w-125" target="ZIlfFuTIaODnUzWRKW0w-124" edge="1">
|
||||||
|
<mxGeometry x="209.35999999999999" y="733" as="geometry">
|
||||||
|
<mxPoint x="240.59" y="584.64" as="sourcePoint"/>
|
||||||
|
<mxPoint x="240.00000000000003" y="473.0000000000002" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="241" y="479"/>
|
||||||
|
<mxPoint x="200" y="480"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-131" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-130" connectable="0" vertex="1">
|
||||||
|
<mxGeometry x="-1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="-30" y="-29" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-132" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-130" connectable="0" vertex="1">
|
||||||
|
<mxGeometry x="1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="18.919999999999582" y="-21.960000000000644" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-133" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><b>Flow</b></p><hr><p style="margin: 0px; margin-left: 8px;"><br></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="630" y="250" width="160" height="78.36" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-134" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="ZIlfFuTIaODnUzWRKW0w-133" target="ZIlfFuTIaODnUzWRKW0w-119" edge="1">
|
||||||
|
<mxGeometry x="408.77" y="598.36" as="geometry">
|
||||||
|
<mxPoint x="440" y="450" as="sourcePoint"/>
|
||||||
|
<mxPoint x="439.41" y="338.36000000000024" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="570" y="290"/>
|
||||||
|
<mxPoint x="570" y="290"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-135" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-134" connectable="0" vertex="1">
|
||||||
|
<mxGeometry x="-1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="-30" y="-20" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-136" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="ZIlfFuTIaODnUzWRKW0w-134" connectable="0" vertex="1">
|
||||||
|
<mxGeometry x="1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="18.919999999999582" y="-21.960000000000644" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="ZIlfFuTIaODnUzWRKW0w-140" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" connectable="0" vertex="1">
|
||||||
|
<mxGeometry x="420.00000020761206" y="99.99999999998707" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="Z9b4y0bF-qkiY6ReSsG3-1" value="<p style="margin: 0px; margin-top: 4px; text-align: center; text-decoration: underline;"><b>Tenant</b></p>" style="verticalAlign=top;align=left;overflow=fill;fontSize=12;fontFamily=Helvetica;html=1;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="40" y="250" width="160" height="78.36" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="Z9b4y0bF-qkiY6ReSsG3-2" value="" style="endArrow=open;endSize=12;startArrow=diamondThin;startSize=14;startFill=0;edgeStyle=orthogonalEdgeStyle;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" parent="1" source="ZIlfFuTIaODnUzWRKW0w-114" target="Z9b4y0bF-qkiY6ReSsG3-1" edge="1">
|
||||||
|
<mxGeometry x="209.35999999999999" y="733" as="geometry">
|
||||||
|
<mxPoint x="230" y="220" as="sourcePoint"/>
|
||||||
|
<mxPoint x="70" y="220" as="targetPoint"/>
|
||||||
|
<Array as="points">
|
||||||
|
<mxPoint x="120" y="220"/>
|
||||||
|
<mxPoint x="121" y="220"/>
|
||||||
|
<mxPoint x="110" y="221"/>
|
||||||
|
</Array>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="Z9b4y0bF-qkiY6ReSsG3-3" value="0..n" style="resizable=0;align=left;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="Z9b4y0bF-qkiY6ReSsG3-2" connectable="0" vertex="1">
|
||||||
|
<mxGeometry x="-1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="450" y="-100" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="Z9b4y0bF-qkiY6ReSsG3-4" value="1" style="resizable=0;align=right;verticalAlign=top;labelBackgroundColor=#ffffff;fontSize=10;strokeColor=#003366;shadow=1;fillColor=#D4E1F5;fontColor=#003366" parent="Z9b4y0bF-qkiY6ReSsG3-2" connectable="0" vertex="1">
|
||||||
|
<mxGeometry x="1" relative="1" as="geometry">
|
||||||
|
<mxPoint x="18.919999999999582" y="-21.960000000000644" as="offset"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
<diagram id="lCZzTbn_7m8qK95pbvvS" name="ER図">
|
||||||
|
<mxGraphModel dx="1900" dy="518" 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="inIyfaXWTM6ArMeTnGn6-1" value="User" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="20" width="180" height="180" as="geometry">
|
||||||
|
<mxRectangle x="310" y="60" width="110" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="inIyfaXWTM6ArMeTnGn6-1" vertex="1">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-3" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-2" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-4" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-2" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-1" vertex="1">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-6" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-5" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-7" value="<b style="border-color: var(--border-color); text-align: center; color: rgb(0, 51, 102);">Tenant_id</b>" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-5" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-1" vertex="1">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-9" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-8" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-10" value="ユーザー名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-8" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-1" vertex="1">
|
||||||
|
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-12" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-11" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-13" value="パスワード(暗号化)" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-11" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-1" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-1" vertex="1">
|
||||||
|
<mxGeometry y="150" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-2" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="NCbWPNvujZOKFJAbQVH6-1" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-3" value="その他情報" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="NCbWPNvujZOKFJAbQVH6-1" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-14" value="Domain" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="240" width="180" height="180" as="geometry">
|
||||||
|
<mxRectangle x="590" y="60" width="100" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-15" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="inIyfaXWTM6ArMeTnGn6-14" vertex="1">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-16" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-15" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-17" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-15" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-24" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-14" vertex="1">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-25" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-24" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="inIyfaXWTM6ArMeTnGn6-26" value="domain_url" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="inIyfaXWTM6ArMeTnGn6-24" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-31" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-14" vertex="1">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-32" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="GH2g80-cBXHe62XdPV4N-31" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-33" value="kintoneユーザーID" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="GH2g80-cBXHe62XdPV4N-31" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-34" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-14" vertex="1">
|
||||||
|
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-35" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="GH2g80-cBXHe62XdPV4N-34" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-36" value="kintoneパスワード" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="GH2g80-cBXHe62XdPV4N-34" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-4" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="inIyfaXWTM6ArMeTnGn6-14" vertex="1">
|
||||||
|
<mxGeometry y="150" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-5" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="NCbWPNvujZOKFJAbQVH6-4" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-6" value="API_Key" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="NCbWPNvujZOKFJAbQVH6-4" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-1" value="<b style="border-color: var(--border-color); color: rgb(0, 51, 102);">Tenant</b>" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="130" y="20" width="180" height="180" as="geometry">
|
||||||
|
<mxRectangle x="40" y="60" width="100" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="QUGX19b9cPh8sdE7zjoR-1" vertex="1">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-3" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-2" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-4" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-2" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="QUGX19b9cPh8sdE7zjoR-1" vertex="1">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-6" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-5" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-7" value="名前" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-5" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="QUGX19b9cPh8sdE7zjoR-1" vertex="1">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-9" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-8" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-10" value="ライセンスキー" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-8" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="QUGX19b9cPh8sdE7zjoR-1" vertex="1">
|
||||||
|
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-12" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-11" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="QUGX19b9cPh8sdE7zjoR-13" value="利用期限" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="QUGX19b9cPh8sdE7zjoR-11" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="xY3sbCmQmCiIU-0kb39k-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="QUGX19b9cPh8sdE7zjoR-1" vertex="1">
|
||||||
|
<mxGeometry y="150" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="xY3sbCmQmCiIU-0kb39k-6" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="xY3sbCmQmCiIU-0kb39k-5" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="xY3sbCmQmCiIU-0kb39k-7" value="その他情報" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="xY3sbCmQmCiIU-0kb39k-5" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8Zu1yShcSHxMs68hy39H-1" value="Flow" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="420" y="480" width="180" height="210" as="geometry">
|
||||||
|
<mxRectangle x="320" y="370" width="60" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8Zu1yShcSHxMs68hy39H-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="8Zu1yShcSHxMs68hy39H-1" vertex="1">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8Zu1yShcSHxMs68hy39H-3" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="8Zu1yShcSHxMs68hy39H-2" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8Zu1yShcSHxMs68hy39H-4" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="8Zu1yShcSHxMs68hy39H-2" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-56" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="8Zu1yShcSHxMs68hy39H-1" vertex="1">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-57" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-56" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-58" value="domain_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-56" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8Zu1yShcSHxMs68hy39H-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="8Zu1yShcSHxMs68hy39H-1" vertex="1">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8Zu1yShcSHxMs68hy39H-6" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="8Zu1yShcSHxMs68hy39H-5" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8Zu1yShcSHxMs68hy39H-7" value="app_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="8Zu1yShcSHxMs68hy39H-5" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8Zu1yShcSHxMs68hy39H-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="8Zu1yShcSHxMs68hy39H-1" vertex="1">
|
||||||
|
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8Zu1yShcSHxMs68hy39H-9" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="8Zu1yShcSHxMs68hy39H-8" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="8Zu1yShcSHxMs68hy39H-10" value="spaceid" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="8Zu1yShcSHxMs68hy39H-8" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="XZEu5LJmFrV2HCpzu9aW-12" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="8Zu1yShcSHxMs68hy39H-1" vertex="1">
|
||||||
|
<mxGeometry y="150" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="XZEu5LJmFrV2HCpzu9aW-13" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="XZEu5LJmFrV2HCpzu9aW-12" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="XZEu5LJmFrV2HCpzu9aW-14" value="content(json)" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="XZEu5LJmFrV2HCpzu9aW-12" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-14" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="8Zu1yShcSHxMs68hy39H-1" vertex="1">
|
||||||
|
<mxGeometry y="180" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-15" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="GH2g80-cBXHe62XdPV4N-14" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-16" value="event_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="GH2g80-cBXHe62XdPV4N-14" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-1" value="Event" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="130" y="480" width="180" height="150" as="geometry">
|
||||||
|
<mxRectangle x="40" y="390" width="70" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="GH2g80-cBXHe62XdPV4N-1" vertex="1">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-3" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-2" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-4" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-2" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="GH2g80-cBXHe62XdPV4N-1" vertex="1">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-6" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-5" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-7" value="画面名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-5" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="GH2g80-cBXHe62XdPV4N-1" vertex="1">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-9" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-8" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-10" value="イベント名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-8" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="GH2g80-cBXHe62XdPV4N-1" vertex="1">
|
||||||
|
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-12" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-11" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-13" value="モバイル使用可能か" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-11" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-17" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;curved=1;" parent="1" source="GH2g80-cBXHe62XdPV4N-2" target="GH2g80-cBXHe62XdPV4N-14" edge="1">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="630" y="845" as="sourcePoint"/>
|
||||||
|
<mxPoint x="530" y="1085" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-18" value="App" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="710" y="280" width="180" height="150" as="geometry">
|
||||||
|
<mxRectangle x="600" y="370" width="60" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-34" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="GH2g80-cBXHe62XdPV4N-18" vertex="1">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-35" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-34" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-36" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-34" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-51" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="GH2g80-cBXHe62XdPV4N-18" vertex="1">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-52" value="UK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-51" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-53" value="domain_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-51" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-25" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="GH2g80-cBXHe62XdPV4N-18" vertex="1">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-26" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-25" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-27" value="app_name" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-25" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-28" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="GH2g80-cBXHe62XdPV4N-18" vertex="1">
|
||||||
|
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-29" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-28" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="GH2g80-cBXHe62XdPV4N-30" value="update_date" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="GH2g80-cBXHe62XdPV4N-28" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-1" value="Flow_History" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="710" y="480" width="180" height="240" as="geometry">
|
||||||
|
<mxRectangle x="320" y="670" width="110" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-23" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-24" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-23" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-25" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-23" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-3" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-2" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-4" value="flow_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-2" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-6" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-5" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-7" value="domain_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-5" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||||
|
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-9" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-8" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-10" value="spaceid" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-8" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||||
|
<mxGeometry y="150" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-12" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-11" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-13" value="appid" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-11" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-17" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||||
|
<mxGeometry y="180" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-18" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="AJLbZMHdl7kzV18ppMoM-17" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-19" value="event_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="AJLbZMHdl7kzV18ppMoM-17" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-14" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-1" vertex="1">
|
||||||
|
<mxGeometry y="210" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-15" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;" parent="AJLbZMHdl7kzV18ppMoM-14" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-16" value="content" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;" parent="AJLbZMHdl7kzV18ppMoM-14" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-40" value="Action" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="130" y="720" width="180" height="150" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-41" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="AJLbZMHdl7kzV18ppMoM-40" vertex="1">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-42" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-41" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-43" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-41" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-44" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-40" vertex="1">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-45" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-44" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-46" value="アクション名" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-44" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-47" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-40" vertex="1">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-48" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-47" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-49" value="説明" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-47" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-50" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="AJLbZMHdl7kzV18ppMoM-40" vertex="1">
|
||||||
|
<mxGeometry y="120" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-51" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-50" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="AJLbZMHdl7kzV18ppMoM-52" value="属性定義(json)" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="AJLbZMHdl7kzV18ppMoM-50" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-7" value="UserDomain" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="710" y="100" width="180" height="120" as="geometry">
|
||||||
|
<mxRectangle x="590" y="60" width="100" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="NCbWPNvujZOKFJAbQVH6-7" vertex="1">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-9" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-8" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-10" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-8" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-11" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="NCbWPNvujZOKFJAbQVH6-7" vertex="1">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-12" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-11" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-13" value="user_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-11" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-44" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="NCbWPNvujZOKFJAbQVH6-7" vertex="1">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-45" value="FK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-44" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-46" value="domain_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=0;overflow=hidden;whiteSpace=wrap;html=1;" parent="NCbWPNvujZOKFJAbQVH6-44" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-42" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERzeroToMany;endFill=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;curved=1;" parent="1" source="QUGX19b9cPh8sdE7zjoR-2" target="inIyfaXWTM6ArMeTnGn6-2" edge="1">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="160" y="350" as="sourcePoint"/>
|
||||||
|
<mxPoint x="260" y="250" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-47" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERmandOne;startArrow=ERmandOne;rounded=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="NCbWPNvujZOKFJAbQVH6-11" target="inIyfaXWTM6ArMeTnGn6-2" edge="1">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="230" y="390" as="sourcePoint"/>
|
||||||
|
<mxPoint x="330" y="290" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-49" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERmandOne;startArrow=ERmandOne;rounded=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="inIyfaXWTM6ArMeTnGn6-15" target="NCbWPNvujZOKFJAbQVH6-44" edge="1">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="200" y="400" as="sourcePoint"/>
|
||||||
|
<mxPoint x="300" y="300" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-50" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERzeroToMany;endFill=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;curved=1;" parent="1" source="inIyfaXWTM6ArMeTnGn6-15" target="NCbWPNvujZOKFJAbQVH6-51" edge="1">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="140" y="430" as="sourcePoint"/>
|
||||||
|
<mxPoint x="740" y="445" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-54" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERzeroToMany;endFill=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;curved=1;" parent="1" source="NCbWPNvujZOKFJAbQVH6-51" target="8Zu1yShcSHxMs68hy39H-5" edge="1">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="210" y="450" as="sourcePoint"/>
|
||||||
|
<mxPoint x="310" y="350" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="NCbWPNvujZOKFJAbQVH6-55" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERzeroToMany;endFill=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;curved=1;" parent="1" source="8Zu1yShcSHxMs68hy39H-2" target="AJLbZMHdl7kzV18ppMoM-2" edge="1">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="270" y="470" as="sourcePoint"/>
|
||||||
|
<mxPoint x="370" y="370" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dFNVox72oq8u18PzFUgJ-1" value="EventAction" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;html=1;fillColor=#f8cecc;strokeColor=#b85450;" parent="1" vertex="1">
|
||||||
|
<mxGeometry x="-110" y="570" width="180" height="120" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dFNVox72oq8u18PzFUgJ-2" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="dFNVox72oq8u18PzFUgJ-1" vertex="1">
|
||||||
|
<mxGeometry y="30" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dFNVox72oq8u18PzFUgJ-3" value="PK" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;fontStyle=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="dFNVox72oq8u18PzFUgJ-2" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dFNVox72oq8u18PzFUgJ-4" value="id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;fontStyle=5;overflow=hidden;whiteSpace=wrap;html=1;" parent="dFNVox72oq8u18PzFUgJ-2" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dFNVox72oq8u18PzFUgJ-5" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="dFNVox72oq8u18PzFUgJ-1" vertex="1">
|
||||||
|
<mxGeometry y="60" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dFNVox72oq8u18PzFUgJ-6" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="dFNVox72oq8u18PzFUgJ-5" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dFNVox72oq8u18PzFUgJ-7" value="event_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="dFNVox72oq8u18PzFUgJ-5" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dFNVox72oq8u18PzFUgJ-8" value="" style="shape=tableRow;horizontal=0;startSize=0;swimlaneHead=0;swimlaneBody=0;fillColor=none;collapsible=0;dropTarget=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="dFNVox72oq8u18PzFUgJ-1" vertex="1">
|
||||||
|
<mxGeometry y="90" width="180" height="30" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dFNVox72oq8u18PzFUgJ-9" value="" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;editable=1;overflow=hidden;whiteSpace=wrap;html=1;" parent="dFNVox72oq8u18PzFUgJ-8" vertex="1">
|
||||||
|
<mxGeometry width="30" height="30" as="geometry">
|
||||||
|
<mxRectangle width="30" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dFNVox72oq8u18PzFUgJ-10" value="action_id" style="shape=partialRectangle;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;overflow=hidden;whiteSpace=wrap;html=1;" parent="dFNVox72oq8u18PzFUgJ-8" vertex="1">
|
||||||
|
<mxGeometry x="30" width="150" height="30" as="geometry">
|
||||||
|
<mxRectangle width="150" height="30" as="alternateBounds"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dFNVox72oq8u18PzFUgJ-16" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERmandOne;startArrow=ERmandOne;entryX=0;entryY=0.5;entryDx=0;entryDy=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;curved=1;" parent="1" source="dFNVox72oq8u18PzFUgJ-5" target="GH2g80-cBXHe62XdPV4N-2" edge="1">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="290" y="800" as="sourcePoint"/>
|
||||||
|
<mxPoint x="390" y="700" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
<mxCell id="dFNVox72oq8u18PzFUgJ-17" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERmandOne;startArrow=ERmandOne;rounded=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;" parent="1" source="AJLbZMHdl7kzV18ppMoM-41" target="dFNVox72oq8u18PzFUgJ-8" edge="1">
|
||||||
|
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||||
|
<mxPoint x="-100" y="900" as="sourcePoint"/>
|
||||||
|
<mxPoint y="800" as="targetPoint"/>
|
||||||
|
</mxGeometry>
|
||||||
|
</mxCell>
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>
|
||||||
|
</diagram>
|
||||||
|
<diagram id="yu2qkxLoxjZt0KdZdE3U" name="ページ3">
|
||||||
|
<mxGraphModel dx="1434" dy="884" 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>
|
||||||
BIN
document/Kinton APIイベント一覧.xlsx
Normal file
BIN
document/Kinton APIイベント一覧.xlsx
Normal file
Binary file not shown.
0
document/Kintone機能整理.drawio
Normal file
0
document/Kintone機能整理.drawio
Normal file
BIN
document/Kintone自動作成ツールのプラグインについて.xlsx
Normal file
BIN
document/Kintone自動作成ツールのプラグインについて.xlsx
Normal file
Binary file not shown.
1047
document/Kintone自動化ツール設計図.drawio
Normal file
1047
document/Kintone自動化ツール設計図.drawio
Normal file
File diff suppressed because one or more lines are too long
BIN
document/action-property.png
Normal file
BIN
document/action-property.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
document/kintone開発自動化 MVP開発内容 SEサービスデザイングループ 2023.pptx
Normal file
BIN
document/kintone開発自動化 MVP開発内容 SEサービスデザイングループ 2023.pptx
Normal file
Binary file not shown.
BIN
document/kintone開発自動化ツール UIデザイン案.pptx
Normal file
BIN
document/kintone開発自動化ツール UIデザイン案.pptx
Normal file
Binary file not shown.
147
document/サイトマップ.drawio
Normal file
147
document/サイトマップ.drawio
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<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>
|
||||||
BIN
document/収支明細管理設計書.xlsx
Normal file
BIN
document/収支明細管理設計書.xlsx
Normal file
Binary file not shown.
BIN
document/日報設計書new.xlsx
Normal file
BIN
document/日報設計書new.xlsx
Normal file
Binary file not shown.
@@ -1,2 +1,3 @@
|
|||||||
KAB_BACKEND_URL="http://127.0.0.1:8000/api/v1/"
|
KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
|
||||||
|
#KAB_BACKEND_URL="http://127.0.0.1:8000/"
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
VUE_BACKEND_URL="http://localhost:8000/api/"
|
#KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
|
||||||
|
KAB_BACKEND_URL="http://127.0.0.1:8000/"
|
||||||
|
|||||||
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@@ -35,3 +35,6 @@ yarn-error.log*
|
|||||||
|
|
||||||
# local .env files
|
# local .env files
|
||||||
.env.local*
|
.env.local*
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm-lock.yaml
|
||||||
|
|||||||
@@ -47,3 +47,7 @@ quasar build
|
|||||||
|
|
||||||
### Customize the configuration
|
### Customize the configuration
|
||||||
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).
|
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).
|
||||||
|
|
||||||
|
## VUE3.0编程规范
|
||||||
|
1. [VUE3.0编程概要](./VUE3.0概要.md)
|
||||||
|
2. [VUE3.0编程规范](./VUE3.0-coding-rule.md)
|
||||||
|
|||||||
241
frontend/VUE3.0-coding-rule.md
Normal file
241
frontend/VUE3.0-coding-rule.md
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
以下是一份 Vue 3 和 TypeScript 的编程规范:
|
||||||
|
|
||||||
|
**1. 组件定义**
|
||||||
|
|
||||||
|
- 推荐使用 `defineComponent` 来定义组件,以获取 TypeScript 的类型支持。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
// 组件选项
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 数据定义**
|
||||||
|
|
||||||
|
- 使用 `reactive` 定义响应式对象。
|
||||||
|
- 使用 `ref` 定义响应式单值。
|
||||||
|
- 使用 `computed` 定义计算属性。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { reactive, ref, computed } from 'vue'
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
count: 0,
|
||||||
|
message: 'Hello Vue 3'
|
||||||
|
})
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
const doubledCount = computed(() => state.count * 2)
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. 生命周期钩子**
|
||||||
|
|
||||||
|
- 使用 `onMounted`、`onUpdated` 等生命周期钩子,而不是 `beforeCreate`、`created`、`beforeMount`、`mounted`、`beforeUpdate`、`updated`、`beforeUnmount`、`unmounted`。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.log('Component is mounted.')
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. 组件通信**
|
||||||
|
|
||||||
|
- 使用 `props` 和 `emit` 实现父子组件通信。
|
||||||
|
- 使用 `provide` 和 `inject` 实现祖先和后代组件通信。
|
||||||
|
- 不再推荐使用 `event bus` 进行任意组件间的通信,可以使用 Vuex 或者全局 `provide`/`inject` 替代。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 父组件
|
||||||
|
<ChildComponent @my-event="handleEvent" />
|
||||||
|
|
||||||
|
// 子组件
|
||||||
|
this.$emit('my-event', eventData)
|
||||||
|
```
|
||||||
|
|
||||||
|
**5. 异步处理**
|
||||||
|
|
||||||
|
- 使用 `async/await` 进行异步处理。
|
||||||
|
- 使用 `Suspense` 和 `async setup()` 处理异步依赖。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const response = await axios.get('https://api.example.com/data')
|
||||||
|
data.value = response.data
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**6. Vue Router 和 Vuex**
|
||||||
|
|
||||||
|
- 使用 Vue Router 4 和 Vuex 4,它们是为 Vue 3 重新设计的。
|
||||||
|
- 使用 `useRouter` 和 `useRoute` 钩子函数在组件中使用 router。
|
||||||
|
- 使用 `useStore` 钩子函数在组件中使用 Vuex store。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { useStore } from 'vuex'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const store = useStore()
|
||||||
|
const router = useRouter()
|
||||||
|
```
|
||||||
|
|
||||||
|
**7. 组件模板**
|
||||||
|
|
||||||
|
- 使用 `v-model` 代替 `.sync` 修饰符进行双向绑定。
|
||||||
|
- 使用 `v-for` 和 `:key` 渲染列表。
|
||||||
|
- 使用 `v-if` 和 `v-else`、`v-else-if` 进行条件渲染。
|
||||||
|
- 使用 `v-on` 或者 `@` 监听事件。
|
||||||
|
- 使用 `v-bind` 或者 `:` 绑定属性。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<div v-if="condition">If block</div>
|
||||||
|
<div v-else>Else block</div>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<button @click="handleClick">Click me</button>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
8. **使用类型注解和接口**
|
||||||
|
|
||||||
|
在 TypeScript 中,尽可能使用类型注解和接口来提供更完善的类型信息和类型检查。这将有助于发现和预防错误。
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface User {
|
||||||
|
name: string;
|
||||||
|
age: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user: User = {
|
||||||
|
name: 'John',
|
||||||
|
age: 30
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
9. **模块化和组件化**
|
||||||
|
|
||||||
|
尽可能将功能和逻辑模块化和组件化,使得代码更易于理解和维护。特别是使用 Composition API 时,可以将公共的逻辑封装成 composable 函数。
|
||||||
|
|
||||||
|
下面是一个使用 `Suspense` 和 `axios` 的示例,这个示例将会从一个 JSON Placeholder API 获取数据:
|
||||||
|
|
||||||
|
首先,我们创建一个 composable 函数,用于获取数据:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
|
export function useAsyncData(url: string) {
|
||||||
|
const data = ref(null)
|
||||||
|
const error = ref(null)
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url)
|
||||||
|
data.value = response.data
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data, error, fetchData }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
然后,我们创建一个 Vue 组件来使用这个函数:
|
||||||
|
|
||||||
|
```js
|
||||||
|
<template>
|
||||||
|
<Suspense>
|
||||||
|
<template #default>
|
||||||
|
<div v-if="error">{{ error.message }}</div>
|
||||||
|
<div v-else>{{ data }}</div>
|
||||||
|
</template>
|
||||||
|
<template #fallback>
|
||||||
|
<div>Loading...</div>
|
||||||
|
</template>
|
||||||
|
</Suspense>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, onMounted } from 'vue'
|
||||||
|
import { useAsyncData } from './composables/useAsyncData'
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
setup() {
|
||||||
|
const { data, error, fetchData } = useAsyncData('https://jsonplaceholder.typicode.com/posts/1')
|
||||||
|
|
||||||
|
onMounted(fetchData)
|
||||||
|
|
||||||
|
return { data, error }
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 在这里添加 CSS 样式 */
|
||||||
|
</style>
|
||||||
|
```
|
||||||
|
|
||||||
|
在这个示例中,我们在 `useAsyncData` 函数中获取数据。当 `onMounted` 钩子函数被调用时(也就是当组件挂载完成后),我们开始获取数据。这个过程是异步的,因此我们在 `Suspense` 组件的 `#fallback` 插槽中显示一个 "Loading..." 的提示。一旦数据加载完成,`Suspense` 组件的 `#default` 插槽就会被渲染,并显示获取到的数据。
|
||||||
|
|
||||||
|
10. **良好的代码风格**
|
||||||
|
|
||||||
|
遵循一致的代码风格和代码质量规则,例如使用 ESLint 和 Prettier 来检查和格式化代码。
|
||||||
|
|
||||||
|
11. **单元测试和端到端测试**
|
||||||
|
|
||||||
|
对关键的组件和函数编写单元测试,对用户的主要操作路径编写端到端测试,确保功能的正确性。
|
||||||
|
|
||||||
|
12. **注释和文档**
|
||||||
|
|
||||||
|
对复杂的逻辑和函数编写注释,提供必要的项目文档,帮助其他开发者理解和使用你的代码。
|
||||||
|
|
||||||
|
13. **以下是 Vue 2 中被废弃或改变的部分API,以及在 Vue 3 中的替代方案:**
|
||||||
|
|
||||||
|
| Vue 2.x | Vue 3.0 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `Vue.set` / `this.$set` | 响应式数据现在是默认的,无需使用这些方法 | Vue 3 的响应式系统是从头开始构建的,所有的对象和数组都是响应式的 |
|
||||||
|
| `Vue.delete` / `this.$delete` | 无需使用这些方法 | 在 Vue 3 中,你只需要使用 `delete` 操作符即可 |
|
||||||
|
| `filters` | 无对应项 | Vue 3 不再支持过滤器,建议使用计算属性或方法替代 |
|
||||||
|
| `Vue.observable` | `reactive` | 用 `reactive` 替代 `Vue.observable`,实现数据的响应式 |
|
||||||
|
| `Vue.prototype` | `app.config.globalProperties` | 在 Vue 3 中,全局 API 已经改变,`Vue.prototype` 被 `app.config.globalProperties` 替代 |
|
||||||
|
| `Vue.component`, `Vue.directive`, `Vue.mixin`, `Vue.use` | `app.component`, `app.directive`, `app.mixin`, `app.use` | 全局注册的 API 改变,如 `Vue.component` 变为 `app.component` |
|
||||||
|
| `beforeDestroy` 和 `destroyed` 生命周期钩子 | `beforeUnmount` 和 `unmounted` | 生命周期钩子名字变化,`beforeDestroy` 和 `destroyed` 分别改为 `beforeUnmount` 和 `unmounted` |
|
||||||
|
| `$on`, `$off`, `$once` | 无对应项 | Event Bus方法被移除,需要用户自行实现或者使用第三方库 |
|
||||||
|
| `v-model` 在自定义组件上使用 | 需要明确的 `modelValue` 和 `update:modelValue` | Vue 3 对 `v-model` 的改动使其在自定义组件上更具灵活性 |
|
||||||
|
| `functional` 选项 | 无对应项 | Vue 3 不再支持函数式组件的写法,而是推荐使用 `render` 函数或 `setup` 函数 |
|
||||||
|
| 异步组件的 `functional` 写法 | `defineAsyncComponent` | 异步组件的创建方式更改,通过 `defineAsyncComponent` 方法创建 |
|
||||||
|
| `destroyed` 和 `beforeDestroy` 钩子函数 | `unmounted` 和 `beforeUnmount` | 生命周期钩子的名称已改为更直观的名称,以更好地表示其在组件实例生命周期中的角色 |
|
||||||
|
| `Vue.extend` | `defineComponent` | Vue 3 使用 `defineComponent` 方法定义组件,有更好的类型推断 |
|
||||||
|
|
||||||
|
注意:Vue 3 对于 Options API 和 Composition API 提供了完全的支持,你可以在一个组件中混合使用这两种 API。不过,为了代码的一致性和可读性,建议在一个项目中选择一种 API 并坚持使用。
|
||||||
|
|
||||||
|
|
||||||
|
- **以下是 Vue 3.0 中的 Composition API 函数的基本说明,以及与 Options API 的对比**
|
||||||
|
|
||||||
|
| Composition API | 说明 | 对应的 Options API |
|
||||||
|
| --------------- | ---- | ------------------ |
|
||||||
|
| `setup` | `setup` 是一个新引入的组件选项,用于使用 Composition API。它是组件内部使用 Composition API 的入口。| 无 |
|
||||||
|
| `ref` | `ref` 函数用于创建一个响应式的数据。它接收一个参数,返回一个响应式的 Ref 对象。| `data` |
|
||||||
|
| `reactive` | `reactive` 函数用于创建一个响应式的对象。它接收一个普通对象,返回一个响应式的对象。| `data` |
|
||||||
|
| `computed` | `computed` 函数用于创建一个计算属性。它接收一个 getter 函数或者一个具有 getter 和 setter 的对象,返回一个响应式的 Ref 对象。| `computed` |
|
||||||
|
| `watch` | `watch` 函数用于响应式地跟踪和触发副作用。它接收一个响应式的源和一个执行副作用的回调函数。| `watch` |
|
||||||
|
| `watchEffect` | `watchEffect` 函数用于立即执行传入的一个函数,并响应式地追踪其依赖,并在其依赖变更时重新运行该函数。 | 无 |
|
||||||
|
| `onMounted` | `onMounted` 函数在组件被挂载时调用。它接收一个在组件挂载后执行的回调函数。 | `mounted` |
|
||||||
|
| `onUnmounted` | `onUnmounted` 函数在组件被卸载时调用。它接收一个在组件卸载后执行的回调函数。 | `beforeDestroy`/`unmounted` |
|
||||||
|
| `onUpdated` | `onUpdated` 在组件更新后调用。它接收一个在组件更新后执行的回调函数。| `updated` |
|
||||||
|
| `provide` | `provide` 函数用于在组件上定义一个可以被后代组件注入的值。它接收一个提供的键和值。 | 有,与 `provide/inject` 相似但是属性而不是函数 |
|
||||||
|
| `inject` | `inject` 函数用于在组件中注入一个由祖先组件提供的值。它接收一个注入的键。 | 有,但与 `provide/inject` 相似但是属性而不是函数 |
|
||||||
|
|
||||||
|
值得注意的是,虽然一些 Composition API 函数与 Options API 的某些选项有相似之处,但它们的工作方式和使用方式可能有所不同。在实际使用中,你需要根据具体的使用场景和需求选择合适的 API。
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="ja-jp">
|
||||||
<head>
|
<head>
|
||||||
<title><%= productName %></title>
|
<title><%= productName %></title>
|
||||||
|
|
||||||
|
|||||||
72
frontend/package-lock.json
generated
72
frontend/package-lock.json
generated
@@ -10,13 +10,16 @@
|
|||||||
"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",
|
||||||
@@ -28,8 +31,9 @@
|
|||||||
"typescript": "^4.5.4"
|
"typescript": "^4.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18 || ^16 || ^14.19",
|
"node": "^20 ||^18 || ^16 || ^14.19",
|
||||||
"npm": ">= 6.13.4",
|
"npm": ">= 6.13.4",
|
||||||
|
"pnpm": ">=8.6.0",
|
||||||
"yarn": ">= 1.21.1"
|
"yarn": ">= 1.21.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -544,6 +548,12 @@
|
|||||||
"@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",
|
||||||
@@ -4070,6 +4080,56 @@
|
|||||||
"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",
|
||||||
@@ -4946,7 +5006,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==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -5045,6 +5105,14 @@
|
|||||||
"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": "kintone-app-builder",
|
"name": "kintone-automate",
|
||||||
"version": "0.0.1",
|
"version": "0.2.0",
|
||||||
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
|
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
|
||||||
"productName": "Kintone App Builder",
|
"productName": "Kintone Automate",
|
||||||
"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,18 +10,24 @@
|
|||||||
"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",
|
||||||
"build": "quasar build"
|
"dev:local": "set \"LOCAL=true\" && quasar dev",
|
||||||
|
"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",
|
||||||
"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",
|
||||||
@@ -33,8 +39,9 @@
|
|||||||
"typescript": "^4.5.4"
|
"typescript": "^4.5.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18 || ^16 || ^14.19",
|
"node": "^20 ||^18 || ^16 || ^14.19",
|
||||||
"npm": ">= 6.13.4",
|
"npm": ">= 6.13.4",
|
||||||
"yarn": ">= 1.21.1"
|
"yarn": ">= 1.21.1",
|
||||||
|
"pnpm": ">=8.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
frontend/public/web.config
Normal file
8
frontend/public/web.config
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<configuration>
|
||||||
|
<system.webServer>
|
||||||
|
<staticContent>
|
||||||
|
<mimeMap fileExtension=".woff" mimeType="application/font-woff" />
|
||||||
|
<mimeMap fileExtension=".woff2" mimeType="application/font-woff2" />
|
||||||
|
</staticContent>
|
||||||
|
</system.webServer>
|
||||||
|
</configuration>
|
||||||
@@ -10,10 +10,14 @@
|
|||||||
|
|
||||||
|
|
||||||
const { configure } = require('quasar/wrappers');
|
const { configure } = require('quasar/wrappers');
|
||||||
const dotenv = require('dotenv').config().parsed;
|
const envPath = process.env.LOCAL==='true'?'.env.development':'.env';
|
||||||
const package = require('./package.json');
|
const dotenv = require('dotenv').config({path:envPath}).parsed;
|
||||||
|
console.log('dotenv=>',dotenv);
|
||||||
|
// const package = require('./package.json');
|
||||||
const { Notify } = require('quasar');
|
const { Notify } = require('quasar');
|
||||||
const version = package.version;
|
const version = process.env.npm_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: {
|
||||||
@@ -32,7 +36,8 @@ 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
|
||||||
@@ -49,7 +54,6 @@ 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
|
||||||
],
|
],
|
||||||
@@ -60,6 +64,7 @@ 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,
|
||||||
@@ -70,7 +75,7 @@ module.exports = configure(function (/* ctx */) {
|
|||||||
|
|
||||||
// publicPath: '/',
|
// publicPath: '/',
|
||||||
// analyze: true,
|
// analyze: true,
|
||||||
env: { ...dotenv, version },
|
env: { ...dotenv, version ,productName},
|
||||||
// rawDefine: {}
|
// rawDefine: {}
|
||||||
// ignorePublicFolder: true,
|
// ignorePublicFolder: true,
|
||||||
// minify: false,
|
// minify: false,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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 {
|
||||||
@@ -15,7 +16,26 @@ declare module '@vue/runtime-core' {
|
|||||||
// "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 });
|
||||||
|
const token=localStorage.getItem('token')||'';
|
||||||
|
if(token!==''){
|
||||||
|
api.defaults.headers["Authorization"]='Bearer ' + token;
|
||||||
|
}
|
||||||
|
//axios例外キャプチャー
|
||||||
|
api.interceptors.response.use(
|
||||||
|
(response)=>response,
|
||||||
|
(error)=>{
|
||||||
|
if (error.response && error.response.status === 401) {
|
||||||
|
// 認証エラーの場合再ログインする
|
||||||
|
console.error('(; ゚Д゚)/認証エラー(401):', error);
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
router.replace({
|
||||||
|
path:"/login",
|
||||||
|
query:{redirect:router.currentRoute.value.fullPath}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
)
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
20
frontend/src/boot/error-handler.ts
Normal file
20
frontend/src/boot/error-handler.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// 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 }) => {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
62
frontend/src/components/ActionSelect.vue
Normal file
62
frontend/src/components/ActionSelect.vue
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||||
|
<q-spinner color="primary" size="3em" />
|
||||||
|
</div>
|
||||||
|
<q-table v-else row-key="index" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows"
|
||||||
|
class="action-table"
|
||||||
|
flat bordered
|
||||||
|
virtual-scroll
|
||||||
|
:pagination="pagination"
|
||||||
|
:rows-per-page-options="[0]"
|
||||||
|
:filter="filter"
|
||||||
|
>
|
||||||
|
</q-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { ref,onMounted,reactive } from 'vue'
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'actionSelect',
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
type: String,
|
||||||
|
filter:String
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const isLoaded=ref(false);
|
||||||
|
const columns = [
|
||||||
|
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
|
||||||
|
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
|
||||||
|
// { name: 'content', label: '内容', field: 'content', sortable: true }
|
||||||
|
];
|
||||||
|
const rows = reactive([])
|
||||||
|
onMounted(async () => {
|
||||||
|
const res =await api.get('api/actions');
|
||||||
|
res.data.forEach((item,index) =>
|
||||||
|
{
|
||||||
|
rows.push({index,name:item.name,desc:item.title,outputPoints:item.outputpoints,property:item.property});
|
||||||
|
});
|
||||||
|
isLoaded.value=true;
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
columns,
|
||||||
|
rows,
|
||||||
|
selected: ref([]),
|
||||||
|
pagination:ref({
|
||||||
|
rowsPerPage:0
|
||||||
|
}),
|
||||||
|
isLoaded,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.action-table{
|
||||||
|
height: 100%;
|
||||||
|
max-height: 540px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
83
frontend/src/components/ActionSetting.vue
Normal file
83
frontend/src/components/ActionSetting.vue
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
<template>
|
||||||
|
<div id="action1">
|
||||||
|
<q-card class="my-card">
|
||||||
|
<q-card-section class="bg-primary text-white">
|
||||||
|
<div class="text-h6">Our Changing Planet</div>
|
||||||
|
<div class="text-subtitle2">by John Doe</div>
|
||||||
|
</q-card-section>
|
||||||
|
<q-separator />
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn flat>設定</q-btn>
|
||||||
|
<q-btn class="del" flat @click="clickdel">削除</q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
<div class="next" style="display: table;width: 100%;height:40px;" @mouseenter="showAdd = true" @mouseleave="()=>{if(!showMenu) showAdd = false;}">
|
||||||
|
<div aria-hidden="false" style="display: table-row;">
|
||||||
|
<div
|
||||||
|
style="display: table-cell;background: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDEzIDMwIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCAyMCA0MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCiA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPi5zdDB7ZmlsbDpub25lO3N0cm9rZTojNUI1QjVDO3N0cm9rZS13aWR0aDoyO3N0cm9rZS1taXRlcmxpbWl0OjEwO30NCgkuc3Qxe2ZpbGw6IzVCNUI1Qzt9PC9zdHlsZT4NCiA8cmVjdCB4PSI5IiB3aWR0aD0iMiIgaGVpZ2h0PSIzNy45NzQiIGZpbGw9IiM1MTUxNTEiIHN0cm9rZS13aWR0aD0iNC41MDYyIi8+DQogPHBvbHlnb24gdHJhbnNmb3JtPSJtYXRyaXgoMS4xNzg1IDAgMCAxLjE3ODUgLS42MDY5MiAyMy41NDQpIiBwb2ludHM9IjEuOTI4IDQuMDY1IDguOTk5IDExLjEzNiAxNi4wNzIgNC4wNjUgMTcuNDg2IDUuNDc5IDguOTk5IDEzLjk2NCAwLjUxNSA1LjQ3OSIgZmlsbD0iIzUxNTE1MSIvPg0KPC9zdmc+DQo=") center center no-repeat;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="showAdd" style="display:table-row;height:inherit;position:absolute;left:50%;">
|
||||||
|
<div style="display:table-cell;">
|
||||||
|
<q-btn round size="xs" color="primary" label="+">
|
||||||
|
<q-menu v-model="showMenu">
|
||||||
|
<q-list style="min-width: 100px">
|
||||||
|
<q-item clickable v-close-popup>
|
||||||
|
<q-item-section @click="clickadd">New tab</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { ref,watch } from 'vue'
|
||||||
|
export default {
|
||||||
|
emits: [
|
||||||
|
'addaction'
|
||||||
|
],
|
||||||
|
setup(props,context) {
|
||||||
|
const showAdd = ref(false)
|
||||||
|
const showMenu = ref(false)
|
||||||
|
|
||||||
|
watch(showMenu,(newVal) =>{
|
||||||
|
console.log('3');
|
||||||
|
if(!newVal)
|
||||||
|
{
|
||||||
|
showAdd.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const clickadd = () => {
|
||||||
|
console.log('3');
|
||||||
|
context.emit('addaction');
|
||||||
|
//let oDiv1 = pdiv;
|
||||||
|
// let oDiv1 = document.getElementById('action1');
|
||||||
|
// let oDiv2 = document.createElement('div');
|
||||||
|
// if (oDiv1 !== null) {
|
||||||
|
// oDiv2.innerHTML = oDiv1?.innerHTML;
|
||||||
|
// oDiv1?.after(oDiv2);
|
||||||
|
// let oAdd = oDiv2.getElementsByClassName('next')[0];
|
||||||
|
// oAdd.addEventListener('mouseenter', mouseenter);
|
||||||
|
// oAdd.addEventListener('mouseleave', mouseleave);
|
||||||
|
// let oDel = oDiv2.getElementsByClassName('del')[0];
|
||||||
|
// oDel.addEventListener('click', clickdel);
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
const clickdel = (event: Event) => {
|
||||||
|
let oBtn = event.target as Element;
|
||||||
|
oBtn.parentElement?.parentElement?.parentElement?.parentElement?.remove();
|
||||||
|
};
|
||||||
|
// window.clickadd = clickadd;
|
||||||
|
// window.clickdel = clickdel;
|
||||||
|
// window.mouseenter = mouseenter;
|
||||||
|
// window.mouseleave = mouseleave;
|
||||||
|
|
||||||
|
return {clickadd, clickdel, showAdd, showMenu }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -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 { promises } from 'dns';
|
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -44,12 +44,13 @@ 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('https://mfu07rkgnb7c.cybozu.com/k/' + app.value);
|
const link= ref(`${authStore.currentDomain.kintoneUrl}/k/${app.value}`);
|
||||||
const getAppInfo = async (appId:string|undefined) => {
|
const getAppInfo = async (appId:string|undefined) => {
|
||||||
if(!appId){
|
if(!appId){
|
||||||
return;
|
return;
|
||||||
@@ -59,7 +60,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('app', {
|
const response = await api.get('api/v1/app', {
|
||||||
params:{
|
params:{
|
||||||
app: appId
|
app: appId
|
||||||
}
|
}
|
||||||
@@ -73,7 +74,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
watch(app, async (newApp) => {
|
watch(app, async (newApp) => {
|
||||||
appinfo.value = await getAppInfo(newApp);
|
appinfo.value = await getAppInfo(newApp);
|
||||||
link.value = 'https://mfu07rkgnb7c.cybozu.com/k/' + newApp;
|
link.value = `${authStore.currentDomain.kintoneUrl}/k/${newApp}`;
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
const linkClick=(ev : MouseEvent)=>{
|
const linkClick=(ev : MouseEvent)=>{
|
||||||
@@ -82,7 +83,7 @@ export default defineComponent({
|
|||||||
};
|
};
|
||||||
onMounted(async ()=>{
|
onMounted(async ()=>{
|
||||||
appinfo.value = await getAppInfo(app.value);
|
appinfo.value = await getAppInfo(app.value);
|
||||||
link.value = 'https://mfu07rkgnb7c.cybozu.com/k/' + app.value;
|
link.value = `${authStore.currentDomain.kintoneUrl}/k/${app.value}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
104
frontend/src/components/AppSelect.vue
Normal file
104
frontend/src/components/AppSelect.vue
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-pa-md" >
|
||||||
|
<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"
|
||||||
|
>
|
||||||
|
<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 } from 'vue'
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
import { LeftDataBus } from './flowEditor/left/DataBus';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AppSelect',
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
type: String,
|
||||||
|
filter:String
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const columns = [
|
||||||
|
{ name: 'id', required: true,label: 'ID',align: 'left',field: 'id',sortable: true},
|
||||||
|
{ 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 :any[]= reactive([]);
|
||||||
|
onMounted( () => {
|
||||||
|
api.get('api/v1/allapps').then(res =>{
|
||||||
|
res.data.apps.forEach((item:any) =>
|
||||||
|
{
|
||||||
|
rows.push({
|
||||||
|
id:item.appId,
|
||||||
|
name:item.name,
|
||||||
|
description:item.description,
|
||||||
|
createdate:dateFormat(item.createdAt)});
|
||||||
|
});
|
||||||
|
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: ref([]),
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
.app-table{
|
||||||
|
height: 100%;
|
||||||
|
max-height: 600px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
107
frontend/src/components/ConditionEditor/ConditionEditor.vue
Normal file
107
frontend/src/components/ConditionEditor/ConditionEditor.vue
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<template>
|
||||||
|
<show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" min-width="60vw" 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>
|
||||||
96
frontend/src/components/ConditionEditor/ConditionObject.vue
Normal file
96
frontend/src/components/ConditionEditor/ConditionObject.vue
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<q-field v-model="selectedObject" labelColor="primary" class="condition-object"
|
||||||
|
:clearable="isSelected" stack-label :dense="true" :outlined="true" >
|
||||||
|
<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 }}
|
||||||
|
</q-chip>
|
||||||
|
</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" width="600px">
|
||||||
|
<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>
|
||||||
|
</show-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, reactive, ref ,watchEffect,computed} from 'vue';
|
||||||
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
|
import ConditionObjects from '../ConditionObjects.vue';
|
||||||
|
import { useFlowEditorStore } from '../../stores/flowEditor';
|
||||||
|
import {IActionFlow,IActionNode,IActionVariable} from '../../types/ActionTypes';
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'ConditionObject',
|
||||||
|
components: {
|
||||||
|
ShowDialog,
|
||||||
|
ConditionObjects
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const appDg = ref();
|
||||||
|
const show = ref(false);
|
||||||
|
const selectedObject = ref(props.modelValue);
|
||||||
|
const store = useFlowEditorStore();
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', selectedObject.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
store,
|
||||||
|
appDg,
|
||||||
|
show,
|
||||||
|
showDg,
|
||||||
|
closeDg,
|
||||||
|
selectedObject,
|
||||||
|
vars:reactive(vars),
|
||||||
|
isSelected,
|
||||||
|
filter
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
.condition-object{
|
||||||
|
min-width: 200px;
|
||||||
|
max-height: 40px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
.selected-obj{
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
269
frontend/src/components/ConditionEditor/NodeCondition.vue
Normal file
269
frontend/src/components/ConditionEditor/NodeCondition.vue
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
<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">
|
||||||
|
<ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"></ConditionObject>
|
||||||
|
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
|
||||||
|
<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 } from 'vue';
|
||||||
|
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
|
||||||
|
import ConditionObject from './ConditionObject.vue';
|
||||||
|
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 operators =computed(()=>{
|
||||||
|
const opts=[];
|
||||||
|
for(const op in Operator){
|
||||||
|
opts.push(Operator[op as keyof typeof Operator]);
|
||||||
|
}
|
||||||
|
return opts;
|
||||||
|
});
|
||||||
|
const tree = reactive(props.conditionTree);
|
||||||
|
|
||||||
|
const conditionString = computed(()=>{
|
||||||
|
return tree.buildConditionString(tree.root);
|
||||||
|
});
|
||||||
|
|
||||||
|
const objectValueOptions=(options:any):any[]=>{
|
||||||
|
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);
|
||||||
|
|
||||||
|
return {
|
||||||
|
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;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
.operator{
|
||||||
|
min-width: 150px;
|
||||||
|
max-height: 40px;
|
||||||
|
padding: 2px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12pt;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
59
frontend/src/components/ConditionObjects.vue
Normal file
59
frontend/src/components/ConditionObjects.vue
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<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="appId"></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 } 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) {
|
||||||
|
|
||||||
|
return {
|
||||||
|
tab: ref('fields'),
|
||||||
|
selected: ref([])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
style="max-width: 400px"
|
style="max-width: 400px"
|
||||||
:url="uploadUrl"
|
:url="uploadUrl"
|
||||||
:label="title"
|
:label="title"
|
||||||
|
:headers="headers"
|
||||||
accept=".csv,.xlsx"
|
accept=".csv,.xlsx"
|
||||||
v-on:rejected="onRejected"
|
v-on:rejected="onRejected"
|
||||||
v-on:uploaded="onUploadFinished"
|
v-on:uploaded="onUploadFinished"
|
||||||
@@ -15,7 +16,10 @@
|
|||||||
</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']);
|
||||||
/**
|
/**
|
||||||
* ファイルアップロードを拒否する時の処理
|
* ファイルアップロードを拒否する時の処理
|
||||||
@@ -67,9 +71,12 @@
|
|||||||
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:"設計書から導入する(csv or excel)",
|
title:"設計書から導入する(csv or excel)",
|
||||||
uploadUrl: `${process.env.KAB_BACKEND_URL}createappfromexcel`
|
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
42
frontend/src/components/DomainSelect.vue
Normal file
42
frontend/src/components/DomainSelect.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<q-table :title="name+'一覧'" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { ref,onMounted,reactive } from 'vue'
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'DomainSelect',
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const columns = [
|
||||||
|
{ name: 'id'},
|
||||||
|
{ name: 'tenantid', required: true,label: 'テナント',align: 'left',field: 'tenantid',sortable: true},
|
||||||
|
{ name: 'name', align: 'center', label: 'ドメイン', field: 'name', sortable: true },
|
||||||
|
{ name: 'url', label: 'URL', field: 'url', sortable: true },
|
||||||
|
{ name: 'kintoneuser', label: 'アカウント', field: 'kintoneuser' }
|
||||||
|
]
|
||||||
|
const rows = reactive([])
|
||||||
|
onMounted( () => {
|
||||||
|
api.get(`api/domains/1`).then(res =>{
|
||||||
|
res.data.forEach((item) =>
|
||||||
|
{
|
||||||
|
rows.push({id:item.id,tenantid:item.tenantid,name:item.name,url:item.url,kintoneuser:item.kintoneuser});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
columns,
|
||||||
|
rows,
|
||||||
|
selected: ref([]),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
43
frontend/src/components/DomainSelector.vue
Normal file
43
frontend/src/components/DomainSelector.vue
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<template>
|
||||||
|
<q-btn-dropdown
|
||||||
|
color="primay"
|
||||||
|
push
|
||||||
|
flat
|
||||||
|
no-caps
|
||||||
|
icon="share"
|
||||||
|
size="md"
|
||||||
|
:label="userStore.currentDomain.domainName"
|
||||||
|
>
|
||||||
|
<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 } from 'vue';
|
||||||
|
const userStore = useAuthStore();
|
||||||
|
const domains = ref<IDomainInfo[]>([]);
|
||||||
|
(async ()=>{
|
||||||
|
domains.value = await userStore.getUserDomains();
|
||||||
|
})();
|
||||||
|
|
||||||
|
const onItemClick=(domain:IDomainInfo)=>{
|
||||||
|
console.log(domain);
|
||||||
|
userStore.setCurrentDomain(domain);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
58
frontend/src/components/FieldList.vue
Normal file
58
frontend/src/components/FieldList.vue
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<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" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { ref, onMounted, reactive } from 'vue'
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'FieldList',
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
type: String,
|
||||||
|
appId: Number,
|
||||||
|
modelValue:Array,
|
||||||
|
filter:String
|
||||||
|
},
|
||||||
|
emits:[
|
||||||
|
'update:modelValue'
|
||||||
|
],
|
||||||
|
setup(props) {
|
||||||
|
const isLoaded = ref(false);
|
||||||
|
const columns = [
|
||||||
|
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: row => row.name, sortable: true },
|
||||||
|
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
|
||||||
|
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
||||||
|
]
|
||||||
|
const rows = reactive([]);
|
||||||
|
onMounted(async () => {
|
||||||
|
const res = await api.get('api/v1/appfields', {
|
||||||
|
params: {
|
||||||
|
app: props.appId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let fields = res.data.properties;
|
||||||
|
console.log(fields);
|
||||||
|
Object.keys(fields).forEach((key) => {
|
||||||
|
const fld = fields[key];
|
||||||
|
rows.push({ name: fld.label, objectType: 'field', ...fld });
|
||||||
|
});
|
||||||
|
isLoaded.value = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
columns,
|
||||||
|
rows,
|
||||||
|
// selected: ref([]),
|
||||||
|
isLoaded
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
54
frontend/src/components/FieldSelect.vue
Normal file
54
frontend/src/components/FieldSelect.vue
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||||
|
<q-spinner color="primary" size="3em" />
|
||||||
|
</div>
|
||||||
|
<q-table v-else row-key="name" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { ref,onMounted,reactive } from 'vue'
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'fieldSelect',
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
type: String,
|
||||||
|
appId:Number
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const isLoaded=ref(false);
|
||||||
|
const columns = [
|
||||||
|
{ name: 'name', required: true,label: 'フィールド名',align: 'left',field: row=>row.name,sortable: true},
|
||||||
|
{ name: 'code', label: 'フィールドコード', align: 'left',field: 'code', sortable: true },
|
||||||
|
{ name: 'type', label: 'フィールドタイプ', align: 'left',field: 'type', sortable: true }
|
||||||
|
]
|
||||||
|
const rows = reactive([])
|
||||||
|
onMounted( async () => {
|
||||||
|
const res = await api.get('api/v1/appfields', {
|
||||||
|
params:{
|
||||||
|
app: props.appId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let fields = res.data.properties;
|
||||||
|
console.log(fields);
|
||||||
|
Object.keys(fields).forEach((key) =>
|
||||||
|
{
|
||||||
|
const fld=fields[key];
|
||||||
|
// rows.push({name:fields[key].label,code:fields[key].code,type:fields[key].type});
|
||||||
|
rows.push({name:fld.label,...fld});
|
||||||
|
});
|
||||||
|
isLoaded.value=true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
columns,
|
||||||
|
rows,
|
||||||
|
selected: ref([]),
|
||||||
|
isLoaded
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
export interface Rule{
|
|
||||||
id:number;
|
|
||||||
name:string;
|
|
||||||
condtion:CondtionTree
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CondtionTree{
|
|
||||||
|
|
||||||
}
|
|
||||||
63
frontend/src/components/ShowDialog.vue
Normal file
63
frontend/src/components/ShowDialog.vue
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<template>
|
||||||
|
<!-- <div class="q-pa-md q-gutter-sm" > -->
|
||||||
|
<q-dialog :model-value="visible" persistent bordered>
|
||||||
|
<q-card :style="cardStyle" >
|
||||||
|
<q-toolbar class="bg-grey-4">
|
||||||
|
<q-toolbar-title>{{ name }}</q-toolbar-title>
|
||||||
|
<q-space></q-space>
|
||||||
|
<slot name="toolbar"></slot>
|
||||||
|
<q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" />
|
||||||
|
</q-toolbar>
|
||||||
|
<q-card-section>
|
||||||
|
<!-- <div class="text-h6">{{ name }}</div> -->
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section class="q-pt-none" :style="sectionStyle">
|
||||||
|
<slot></slot>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="right" class="text-primary">
|
||||||
|
<q-btn flat label="確定" v-close-popup @click="CloseDialogue('OK')" />
|
||||||
|
<q-btn flat label="キャンセル" v-close-popup @click="CloseDialogue('Cancel')" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
<!-- </div> -->
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import {computed} from 'vue'
|
||||||
|
export default {
|
||||||
|
name: 'ShowDialog',
|
||||||
|
props: {
|
||||||
|
name:String,
|
||||||
|
visible: Boolean,
|
||||||
|
width:String,
|
||||||
|
height:String,
|
||||||
|
minWidth:String,
|
||||||
|
minHeight:String
|
||||||
|
},
|
||||||
|
emits: [
|
||||||
|
'close'
|
||||||
|
],
|
||||||
|
setup(props, context) {
|
||||||
|
const CloseDialogue = (val) => {
|
||||||
|
context.emit('update:visible', false);
|
||||||
|
context.emit('close', val);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardStyle = computed(() => ({
|
||||||
|
minWidth: props.minWidth,
|
||||||
|
width: props.width
|
||||||
|
}));
|
||||||
|
|
||||||
|
const sectionStyle = computed(() => ({
|
||||||
|
height: props.height,
|
||||||
|
minHeight: props.minHeight
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
CloseDialogue,
|
||||||
|
cardStyle,
|
||||||
|
sectionStyle
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
44
frontend/src/components/VariableList.vue
Normal file
44
frontend/src/components/VariableList.vue
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<q-table flat bordered row-key="name" :selection="type"
|
||||||
|
:selected="modelValue"
|
||||||
|
@update:selected="$emit('update:modelValue', $event)"
|
||||||
|
:columns="columns" :rows="rows" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { ref, reactive, PropType, compile } from 'vue';
|
||||||
|
import {IActionNode,IActionVariable} from '../types/ActionTypes';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'VariableList',
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
type: String,
|
||||||
|
vars:{
|
||||||
|
type:Array as PropType<IActionVariable[]>,
|
||||||
|
reqired:true,
|
||||||
|
default:()=>[]
|
||||||
|
},
|
||||||
|
modelValue:Array
|
||||||
|
},
|
||||||
|
emits:[
|
||||||
|
'update:modelValue'
|
||||||
|
],
|
||||||
|
setup(props) {
|
||||||
|
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: 'name',required: true, sortable: true }
|
||||||
|
];
|
||||||
|
const rows= props.vars.map((v)=>{
|
||||||
|
return {objectType:'variable',...v};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
columns,
|
||||||
|
rows:reactive(rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
29
frontend/src/components/flowEditor/left/ControlPanelC.vue
Normal file
29
frontend/src/components/flowEditor/left/ControlPanelC.vue
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<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>
|
||||||
72
frontend/src/components/flowEditor/left/DataBus.ts
Normal file
72
frontend/src/components/flowEditor/left/DataBus.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
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[]
|
||||||
|
}
|
||||||
42
frontend/src/components/flowEditor/left/ItemSelector.vue
Normal file
42
frontend/src/components/flowEditor/left/ItemSelector.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<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>
|
||||||
94
frontend/src/components/left/AppSelector.vue
Normal file
94
frontend/src/components/left/AppSelector.vue
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<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="680px" min-height="600px" height="600px">
|
||||||
|
<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>
|
||||||
|
<AppSelect ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelect>
|
||||||
|
</ShowDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent,ref } from 'vue';
|
||||||
|
import {AppInfo} from '../../types/ActionTypes'
|
||||||
|
import ShowDialog from '../../components/ShowDialog.vue';
|
||||||
|
import AppSelect from '../../components/AppSelect.vue';
|
||||||
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
|
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'AppSelector',
|
||||||
|
emits:[
|
||||||
|
"appSelected"
|
||||||
|
],
|
||||||
|
components:{
|
||||||
|
AppSelect,
|
||||||
|
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>
|
||||||
151
frontend/src/components/left/EventTree.vue
Normal file
151
frontend/src/components/left/EventTree.vue
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
<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 class="row col items-start 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="selectedEvent && prop.node.eventId===selectedEvent.eventId?'selected-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">{{ 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>
|
||||||
|
</q-tree>
|
||||||
|
<show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg" widht="400px">
|
||||||
|
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
|
||||||
|
</show-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, computed, ref } from 'vue';
|
||||||
|
import { IKintoneEvent ,IKintoneEventGroup, IKintoneEventNode, kintoneEvent} from '../../types/KintoneEvents';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
|
import { ActionFlow, ActionNode, RootAction } from 'src/types/ActionTypes';
|
||||||
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
|
import FieldSelect from '../FieldSelect.vue';
|
||||||
|
import { QTree } from 'quasar';
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'EventTree',
|
||||||
|
components: {
|
||||||
|
ShowDialog,
|
||||||
|
FieldSelect,
|
||||||
|
},
|
||||||
|
setup(props, context) {
|
||||||
|
const appDg = ref();
|
||||||
|
const store = useFlowEditorStore();
|
||||||
|
const showDialog = ref(false);
|
||||||
|
const tree = ref<QTree>();
|
||||||
|
// const eventTree=ref(kintoneEvents);
|
||||||
|
// const selectedFlow = store.currentFlow;
|
||||||
|
|
||||||
|
// const expanded=ref();
|
||||||
|
const selectedEvent = ref<IKintoneEvent|null>(null);
|
||||||
|
const selectedChangeEvent=ref<IKintoneEventGroup|null>(null);
|
||||||
|
const isFieldChange = (node:IKintoneEventNode)=>{
|
||||||
|
return node.header=='EVENT' && node.eventId.indexOf(".change.")>-1;
|
||||||
|
}
|
||||||
|
//フィールド値変更イベント追加
|
||||||
|
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.label,
|
||||||
|
eventid,
|
||||||
|
selectedChangeEvent.value.eventId)
|
||||||
|
);
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
// eventTree,
|
||||||
|
// expanded,
|
||||||
|
appDg,
|
||||||
|
tree,
|
||||||
|
showDialog,
|
||||||
|
isFieldChange,
|
||||||
|
onSelected,
|
||||||
|
selectedEvent,
|
||||||
|
addChangeEvent,
|
||||||
|
closeDg,
|
||||||
|
store
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</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;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
276
frontend/src/components/main/NodeItem.vue
Normal file
276
frontend/src/components/main/NodeItem.vue
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
<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;
|
||||||
|
};
|
||||||
|
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>
|
||||||
168
frontend/src/components/main/NodeLine.vue
Normal file
168
frontend/src/components/main/NodeLine.vue
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
<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,4 +32,3 @@ export interface AppInfo {
|
|||||||
creator?:User;
|
creator?:User;
|
||||||
modifier?:User;
|
modifier?:User;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
75
frontend/src/components/right/ActionProperty.vue
Normal file
75
frontend/src/components/right/ActionProperty.vue
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<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>
|
||||||
0
frontend/src/components/right/AppFieldSelect.vue
Normal file
0
frontend/src/components/right/AppFieldSelect.vue
Normal file
95
frontend/src/components/right/ConditionInput.vue
Normal file
95
frontend/src/components/right/ConditionInput.vue
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<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" @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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref ,watchEffect,computed,reactive} from 'vue';
|
||||||
|
import { ConditionTree,GroupNode,ConditionNode,LogicalOperator,Operator } from 'app/src/types/Conditions';
|
||||||
|
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue'
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'FieldInput',
|
||||||
|
components: {
|
||||||
|
ConditionEditor
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
displayName:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hint:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
setup(props, { emit }) {
|
||||||
|
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({},Operator.Equal,'',tree.root);
|
||||||
|
tree.addNode(tree.root,newNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSetted=ref(props.modelValue && props.modelValue!=='');
|
||||||
|
|
||||||
|
const conditionString = computed(()=>{
|
||||||
|
return tree.buildConditionString(tree.root);
|
||||||
|
});
|
||||||
|
|
||||||
|
const showDg = () => {
|
||||||
|
show.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClosed = (val:string) => {
|
||||||
|
if (val == 'OK') {
|
||||||
|
const conditionJson = tree.toJson();
|
||||||
|
isSetted.value=true;
|
||||||
|
emit('update:modelValue', conditionJson);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
const conditionJson = tree.toJson();
|
||||||
|
emit('update:modelValue', conditionJson);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
appDg,
|
||||||
|
isSetted,
|
||||||
|
show,
|
||||||
|
showDg,
|
||||||
|
onClosed,
|
||||||
|
tree,
|
||||||
|
conditionString
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
57
frontend/src/components/right/DatePicker.vue
Normal file
57
frontend/src/components/right/DatePicker.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" label-color="primary" mask="date" :rules="['date']" 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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref ,watchEffect} from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DatePicker',
|
||||||
|
props: {
|
||||||
|
displayName:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hint:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const selectedDate = ref(props.modelValue);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', selectedDate.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedDate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
80
frontend/src/components/right/EventSetter.vue
Normal file
80
frontend/src/components/right/EventSetter.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-btn round dense flat icon="add" @click="addButtonEvent()" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</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',
|
||||||
|
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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setup(props , { emit }) {
|
||||||
|
const inputValue = ref(props.modelValue);
|
||||||
|
const store = useFlowEditorStore();
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', inputValue.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
inputValue,
|
||||||
|
addButtonEvent
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
98
frontend/src/components/right/FieldInput.vue
Normal file
98
frontend/src/components/right/FieldInput.vue
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<template>
|
||||||
|
<q-field v-model="selectedField" :label="displayName" labelColor="primary"
|
||||||
|
:clearable="isSelected" stack-label :bottom-slots="!isSelected" >
|
||||||
|
<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">
|
||||||
|
<div> 項目コード:<q-chip size="sm" outline color="secondary" text-color="white">{{selectedField.code}}</q-chip></div>
|
||||||
|
</template> -->
|
||||||
|
<template v-slot:hint v-if="!isSelected">
|
||||||
|
{{ placeholder }}
|
||||||
|
</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" widht="400px">
|
||||||
|
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
|
||||||
|
</show-dialog>
|
||||||
|
</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',
|
||||||
|
components: {
|
||||||
|
ShowDialog,
|
||||||
|
FieldSelect,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
displayName:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hint:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const appDg = ref();
|
||||||
|
const show = ref(false);
|
||||||
|
const selectedField = ref(props.modelValue);
|
||||||
|
const store = useFlowEditorStore();
|
||||||
|
const isSelected = computed(()=>{
|
||||||
|
return selectedField.value!==null && typeof selectedField.value === 'object' && ('name' in selectedField.value)
|
||||||
|
});
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
45
frontend/src/components/right/InputText.vue
Normal file
45
frontend/src/components/right/InputText.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent,ref,watchEffect } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'InputText',
|
||||||
|
props: {
|
||||||
|
displayName:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hint:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
setup(props , { emit }) {
|
||||||
|
const inputValue = ref(props.modelValue);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', inputValue.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
inputValue,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
45
frontend/src/components/right/MuiltInputText.vue
Normal file
45
frontend/src/components/right/MuiltInputText.vue
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<template>
|
||||||
|
<q-input :label="displayName" label-color="primary" v-model="inputValue" :placeholder="placeholder" autogrow stack-label/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent,ref,watchEffect } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'MuiltInputText',
|
||||||
|
props: {
|
||||||
|
displayName:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hint:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const inputValue = ref(props.modelValue);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', inputValue.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
inputValue,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
66
frontend/src/components/right/PropertyList.vue
Normal file
66
frontend/src/components/right/PropertyList.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-for="(item, index) in properties" :key="index" >
|
||||||
|
<component :is="item.component" v-bind="item.props" :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 MuiltInputText from '../right/MuiltInputText.vue';
|
||||||
|
import ConditionInput from '../right/ConditionInput.vue';
|
||||||
|
import EventSetter from '../right/EventSetter.vue';
|
||||||
|
import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'PropertyList',
|
||||||
|
components: {
|
||||||
|
InputText,
|
||||||
|
SelectBox,
|
||||||
|
DatePicker,
|
||||||
|
FieldInput,
|
||||||
|
MuiltInputText,
|
||||||
|
ConditionInput,
|
||||||
|
EventSetter
|
||||||
|
},
|
||||||
|
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={};
|
||||||
|
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>
|
||||||
80
frontend/src/components/right/PropertyPanel.vue
Normal file
80
frontend/src/components/right/PropertyPanel.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<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-card class="column full-height" style="width: 300px">
|
||||||
|
<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="更新" @click="save" outline dense padding="none sm" color="primary" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { ref,defineComponent, PropType ,watchEffect} from 'vue'
|
||||||
|
import PropertyList from 'components/right/PropertyList.vue';
|
||||||
|
import { IActionNode } 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'
|
||||||
|
],
|
||||||
|
setup(props,{emit}) {
|
||||||
|
const showPanel =ref(props.drawerRight);
|
||||||
|
const actionProps =ref(props.actionNode?.actionProps);
|
||||||
|
watchEffect(() => {
|
||||||
|
showPanel.value = props.drawerRight;
|
||||||
|
actionProps.value= props.actionNode?.actionProps;
|
||||||
|
});
|
||||||
|
|
||||||
|
const cancel = async() =>{
|
||||||
|
showPanel.value = false;
|
||||||
|
emit('update:drawerRight',false )
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = async () =>{
|
||||||
|
showPanel.value=false;
|
||||||
|
emit('update:drawerRight',false )
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
cancel,
|
||||||
|
save,
|
||||||
|
actionProps,
|
||||||
|
showPanel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss">
|
||||||
|
</style>
|
||||||
40
frontend/src/components/right/SelectBox.vue
Normal file
40
frontend/src/components/right/SelectBox.vue
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<template>
|
||||||
|
<q-select v-model="selectedValue" :label="displayName" :options="options"/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent,ref,watchEffect } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'SelectBox',
|
||||||
|
props: {
|
||||||
|
displayName:{
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const selectedValue = ref(props.modelValue);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', selectedValue.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
selectedValue
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
22
frontend/src/control/auth.ts
Normal file
22
frontend/src/control/auth.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
57
frontend/src/control/flowctrl.ts
Normal file
57
frontend/src/control/flowctrl.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
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 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 +1,25 @@
|
|||||||
// 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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,14 +11,16 @@
|
|||||||
@click="toggleLeftDrawer"
|
@click="toggleLeftDrawer"
|
||||||
/>
|
/>
|
||||||
<q-toolbar-title>
|
<q-toolbar-title>
|
||||||
Kintone App Builder
|
{{ productName }}
|
||||||
<q-badge align="top" outline>V{{ env.version }}</q-badge>
|
<q-badge align="top" outline>V{{ 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
|
<q-drawer
|
||||||
v-model="leftDrawerOpen"
|
:model-value="authStore.toggleLeftDrawer"
|
||||||
:show-if-above="false"
|
:show-if-above="false"
|
||||||
bordered
|
bordered
|
||||||
>
|
>
|
||||||
@@ -26,7 +28,7 @@
|
|||||||
<q-item-label
|
<q-item-label
|
||||||
header
|
header
|
||||||
>
|
>
|
||||||
Essential Links
|
関連リンク
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
|
|
||||||
<EssentialLink
|
<EssentialLink
|
||||||
@@ -46,6 +48,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } 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[] = [
|
||||||
{
|
{
|
||||||
@@ -56,10 +62,17 @@ const essentialLinks: EssentialLinkProps[] = [
|
|||||||
target:'_self'
|
target:'_self'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'ルールエディター',
|
title: 'フローエディター',
|
||||||
caption: 'rule',
|
caption: 'flowChart',
|
||||||
icon: 'rule',
|
icon: 'account_tree',
|
||||||
link: '/#/ruleEditor',
|
link: '/#/FlowChart',
|
||||||
|
target:'_self'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '条件エディター',
|
||||||
|
caption: 'condition',
|
||||||
|
icon: 'tune',
|
||||||
|
link: '/#/condition',
|
||||||
target:'_self'
|
target:'_self'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -138,11 +151,10 @@ const essentialLinks: EssentialLinkProps[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const leftDrawerOpen = ref(false)
|
const version = process.env.version;
|
||||||
|
const productName = process.env.productName;
|
||||||
const env=process.env;
|
|
||||||
|
|
||||||
function toggleLeftDrawer() {
|
function toggleLeftDrawer() {
|
||||||
leftDrawerOpen.value = !leftDrawerOpen.value
|
authStore.toggleLeftMenu();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
274
frontend/src/pages/FlowChart.vue
Normal file
274
frontend/src/pages/FlowChart.vue
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<template>
|
||||||
|
<q-page>
|
||||||
|
<q-layout container class="absolute-full shadow-2 rounded-borders">
|
||||||
|
<div class="q-pa-sm q-gutter-sm ">
|
||||||
|
<q-drawer side="left" :overlay="true" bordered v-model="drawerLeft" :show-if-above="false" elevated>
|
||||||
|
<div class="flex-center fixed-top app-selector">
|
||||||
|
<AppSelector />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-center absolute-full" style="padding-top:65px;padding-left:15px;padding-right:15px;">
|
||||||
|
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '0' }">
|
||||||
|
<EventTree />
|
||||||
|
</q-scroll-area>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-center fixed-bottom bg-grey-3 q-pa-md row ">
|
||||||
|
<q-btn color="secondary" glossy label="デプロイ" @click="onDeploy" icon="sync" :loading="deployLoading" />
|
||||||
|
<q-space></q-space>
|
||||||
|
<q-btn color="primary" label="保存" @click="onSaveFlow" icon="save" :loading="saveLoading" />
|
||||||
|
</div>
|
||||||
|
</q-drawer>
|
||||||
|
</div>
|
||||||
|
<q-btn flat dense round
|
||||||
|
:icon="drawerLeft?'keyboard_double_arrow_left':'keyboard_double_arrow_right'"
|
||||||
|
:style="[drawerLeft?{'left':'300px'}:{'left':'0px'}]"
|
||||||
|
@click="drawerLeft=!drawerLeft" class="expand" />
|
||||||
|
<div class="q-pa-md q-gutter-sm" :style="{minWidth: minPanelWidth}">
|
||||||
|
<div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft?{paddingLeft:'300px'}:{}]">
|
||||||
|
<node-item v-if="rootNode!==undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode"
|
||||||
|
:actionNode="rootNode" @addNode="addNode" @nodeSelected="onNodeSelected" @nodeEdit="onNodeEdit"
|
||||||
|
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<PropertyPanel :actionNode="store.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
|
||||||
|
</q-layout>
|
||||||
|
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg" min-width="500px" min-height="500px">
|
||||||
|
<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>
|
||||||
|
<action-select ref="appDg" name="model" :filter="filter" type="single"></action-select>
|
||||||
|
</ShowDialog>
|
||||||
|
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, onMounted } from 'vue';
|
||||||
|
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
|
|
||||||
|
import NodeItem from 'src/components/main/NodeItem.vue';
|
||||||
|
import ShowDialog from 'components/ShowDialog.vue';
|
||||||
|
import ActionSelect from 'components/ActionSelect.vue';
|
||||||
|
import PropertyPanel from 'components/right/PropertyPanel.vue';
|
||||||
|
import AppSelector from 'components/left/AppSelector.vue';
|
||||||
|
import EventTree from 'components/left/EventTree.vue';
|
||||||
|
import { FlowCtrl } from '../control/flowctrl';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
const deployLoading = ref(false);
|
||||||
|
const saveLoading = ref(false);
|
||||||
|
const drawerLeft = ref(false);
|
||||||
|
const $q = useQuasar();
|
||||||
|
const store = useFlowEditorStore();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const appDg = ref();
|
||||||
|
const prevNodeIfo = ref({
|
||||||
|
prevNode: {} as IActionNode,
|
||||||
|
inputPoint: ""
|
||||||
|
});
|
||||||
|
// const refFlow = ref<ActionFlow|null>(null);
|
||||||
|
const showAddAction = ref(false);
|
||||||
|
const drawerRight = ref(false);
|
||||||
|
const filter=ref("");
|
||||||
|
const model = ref("");
|
||||||
|
const addActionNode = (action: IActionNode) => {
|
||||||
|
// refFlow.value?.actionNodes.push(action);
|
||||||
|
store.currentFlow?.actionNodes.push(action);
|
||||||
|
}
|
||||||
|
const rootNode = computed(()=>{
|
||||||
|
return store.currentFlow?.getRoot();
|
||||||
|
});
|
||||||
|
const minPanelWidth=computed(()=>{
|
||||||
|
const root = store.currentFlow?.getRoot();
|
||||||
|
if(store.currentFlow && root){
|
||||||
|
return store.currentFlow?.getColumns(root) * 300 + 'px';
|
||||||
|
}else{
|
||||||
|
return "300px";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const addNode = (node: IActionNode, inputPoint: string) => {
|
||||||
|
if (drawerRight.value) {
|
||||||
|
drawerRight.value = false;
|
||||||
|
}
|
||||||
|
showAddAction.value = true;
|
||||||
|
prevNodeIfo.value.prevNode = node;
|
||||||
|
prevNodeIfo.value.inputPoint = inputPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onNodeSelected = (node: IActionNode) => {
|
||||||
|
//右パネルが開いている場合、自動閉じる
|
||||||
|
if (drawerRight.value && store.activeNode?.id !== node.id) {
|
||||||
|
drawerRight.value = false;
|
||||||
|
}
|
||||||
|
store.setActiveNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onNodeEdit = (node: IActionNode) => {
|
||||||
|
store.setActiveNode(node);
|
||||||
|
drawerRight.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDeleteNode = (node: IActionNode) => {
|
||||||
|
if (!store.currentFlow) return;
|
||||||
|
//右パネルが開いている場合、自動閉じる
|
||||||
|
if (drawerRight.value && store.activeNode?.id === node.id) {
|
||||||
|
drawerRight.value = false;
|
||||||
|
}
|
||||||
|
store.currentFlow?.removeNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDeleteAllNextNodes = (node: IActionNode) => {
|
||||||
|
if (!store.currentFlow) return;
|
||||||
|
//右パネルが開いている場合、自動閉じる
|
||||||
|
if (drawerRight.value) {
|
||||||
|
drawerRight.value = false;
|
||||||
|
}
|
||||||
|
store.currentFlow?.removeAllNext(node.id);
|
||||||
|
}
|
||||||
|
const closeDg = (val: any) => {
|
||||||
|
console.log("Dialog closed->", val);
|
||||||
|
if (val == 'OK') {
|
||||||
|
const data = appDg.value.selected[0];
|
||||||
|
const actionProps = JSON.parse(data.property);
|
||||||
|
const outputPoint = JSON.parse(data.outputPoints);
|
||||||
|
const action = new ActionNode(data.name, data.desc, "", outputPoint, actionProps);
|
||||||
|
store.currentFlow?.addNode(action, prevNodeIfo.value.prevNode, prevNodeIfo.value.inputPoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
*フローのデータをコピーする
|
||||||
|
*/
|
||||||
|
const onCopyFlow = () => {
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
const jsonData =JSON.stringify(store.currentFlow) ;
|
||||||
|
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 onDeploy = async () => {
|
||||||
|
if (store.appInfo === undefined || store.flows?.length === 0) {
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
caption: "エラー",
|
||||||
|
message: `設定されたフローがありません。`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
deployLoading.value = true;
|
||||||
|
await store.deploy();
|
||||||
|
deployLoading.value = false;
|
||||||
|
$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
caption: "通知",
|
||||||
|
message: `デプロイを成功しました。`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
deployLoading.value = false;
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
caption: "エラー",
|
||||||
|
message: `デプロイが失敗しました。`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSaveFlow = async () => {
|
||||||
|
const targetFlow = store.selectedFlow;
|
||||||
|
if (targetFlow === undefined) {
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
caption: "エラー",
|
||||||
|
message: `編集中のフローがありません。`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
saveLoading.value = true;
|
||||||
|
await store.saveFlow(targetFlow);
|
||||||
|
saveLoading.value = false;
|
||||||
|
$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
caption: "通知",
|
||||||
|
message: `${targetFlow.getRoot()?.subTitle}のフロー設定を保存しました。`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
saveLoading.value = false;
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
caption: "エラー",
|
||||||
|
message: `${targetFlow.getRoot()?.subTitle}のフローの設定の保存が失敗しました。`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
drawerLeft.value = true;
|
||||||
|
if (store.appInfo === undefined) return;
|
||||||
|
const flowCtrl = new FlowCtrl();
|
||||||
|
const actionFlows = await flowCtrl.getFlows(store.appInfo?.appId);
|
||||||
|
if (actionFlows && actionFlows.length > 0) {
|
||||||
|
store.setFlows(actionFlows);
|
||||||
|
}
|
||||||
|
if (actionFlows && actionFlows.length == 1) {
|
||||||
|
store.selectFlow(actionFlows[0]);
|
||||||
|
}
|
||||||
|
const root = actionFlows[0].getRoot();
|
||||||
|
if (root) {
|
||||||
|
store.setActiveNode(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
authStore.toggleLeftMenu();
|
||||||
|
fetchData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.app-selector {
|
||||||
|
padding: 15px;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flowchart {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-toolbar {
|
||||||
|
opacity: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.event-tree .q-drawer {
|
||||||
|
top: 50px;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
.expand{
|
||||||
|
position: fixed;
|
||||||
|
left: 0px;
|
||||||
|
top: 50%;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
102
frontend/src/pages/FlowChartTest.vue
Normal file
102
frontend/src/pages/FlowChartTest.vue
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<template>
|
||||||
|
<q-page>
|
||||||
|
|
||||||
|
<div class="flowchart">
|
||||||
|
<node-item v-for="(node,) in refFlow.actionNodes" :key="node.id"
|
||||||
|
:isSelected="node===state.activeNode" :actionNode="node"
|
||||||
|
@addNode="addNode"
|
||||||
|
@nodeSelected="onNodeSelected"
|
||||||
|
@nodeEdit="onNodeEdit"
|
||||||
|
@deleteNode="onDeleteNode"
|
||||||
|
@deleteAllNextNodes="onDeleteAllNextNodes"
|
||||||
|
></node-item>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
|
<PropertyPanel :actionNode="state.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
|
||||||
|
<show-dialog v-model:visible="showAddAction" name="アクション" @close="closeDg" width="350px">
|
||||||
|
<action-select ref="appDg" name="アクション" type="single"></action-select>
|
||||||
|
</show-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {ref,reactive,computed} from 'vue';
|
||||||
|
import {IActionNode, ActionNode, IActionFlow, ActionFlow,RootAction, IActionProperty } from 'src/types/ActionTypes';
|
||||||
|
import NodeItem from 'src/components/main/NodeItem.vue';
|
||||||
|
import ShowDialog from 'components/ShowDialog.vue';
|
||||||
|
import ActionSelect from 'components/ActionSelect.vue';
|
||||||
|
import PropertyPanel from 'components/right/PropertyPanel.vue';
|
||||||
|
|
||||||
|
|
||||||
|
const rootNode:RootAction =new RootAction("app.record.create.submit","レコード追加画面","保存するとき");
|
||||||
|
const actionFlow: ActionFlow = new ActionFlow(rootNode);
|
||||||
|
const saibanProps:IActionProperty[]=[{
|
||||||
|
component:"InputText",
|
||||||
|
props:{
|
||||||
|
displayName:"フォーマット",
|
||||||
|
modelValue:"",
|
||||||
|
name:"format",
|
||||||
|
placeholder:"フォーマットを入力してください",
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
component:"FieldInput",
|
||||||
|
props:{
|
||||||
|
displayName:"採番項目",
|
||||||
|
modelValue:"",
|
||||||
|
name:"field",
|
||||||
|
placeholder:"採番項目を選択してください",
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
|
||||||
|
actionFlow.addNode(new ActionNode('自動採番','文書番号を自動採番する','',[],saibanProps));
|
||||||
|
actionFlow.addNode(new ActionNode('入力データ取得','電話番号を取得する',''));
|
||||||
|
const branchNode = actionFlow.addNode(new ActionNode('条件分岐','電話番号入力形式チャック','',['はい','いいえ'] ));
|
||||||
|
// actionFlow.addNode(new ActionNode('入力データ取得','住所を取得する',''),branchNode,'はい');
|
||||||
|
actionFlow.addNode(new ActionNode('エラー表示','エラー表示して保存しない',''),branchNode,'いいえ' );
|
||||||
|
|
||||||
|
// ref関数を使ってtemplateとバインド
|
||||||
|
const state=reactive({
|
||||||
|
activeNode:rootNode,
|
||||||
|
})
|
||||||
|
|
||||||
|
const refFlow = ref(actionFlow);
|
||||||
|
const showAddAction=ref(false);
|
||||||
|
const drawerRight=ref(false);
|
||||||
|
|
||||||
|
const addActionNode=(action:IActionNode)=>{
|
||||||
|
refFlow.value.actionNodes.push(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
const addNode=(node:IActionNode,inputPoint:string)=>{
|
||||||
|
showAddAction.value=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onNodeSelected=(node:IActionNode)=>{
|
||||||
|
//右パネルが開いている場合、自動閉じる
|
||||||
|
if(drawerRight.value && state.activeNode.id!==node.id){
|
||||||
|
drawerRight.value=false;
|
||||||
|
}
|
||||||
|
state.activeNode = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onNodeEdit=(node:IActionNode)=>{
|
||||||
|
state.activeNode = node;
|
||||||
|
drawerRight.value=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDeleteNode=(node:IActionNode)=>{
|
||||||
|
refFlow.value.removeNode(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDeleteAllNextNodes=(node:IActionNode)=>{
|
||||||
|
refFlow.value.removeAllNext(node.id);
|
||||||
|
}
|
||||||
|
const closeDg=(val :any)=>{
|
||||||
|
console.log("Dialog closed->",val);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.flowchart{
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
122
frontend/src/pages/FlowEditorPage.vue
Normal file
122
frontend/src/pages/FlowEditorPage.vue
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="q-ma-md">
|
||||||
|
<div class="q-gutter-xs row items-start">
|
||||||
|
<q-breadcrumbs class="q-pt-xs q-mr-sm" active-color="black">
|
||||||
|
<q-breadcrumbs-el icon="home" />
|
||||||
|
<q-breadcrumbs-el :label="actName" />
|
||||||
|
<q-breadcrumbs-el
|
||||||
|
v-for="flowName in flowNames"
|
||||||
|
:key="flowName"
|
||||||
|
:label="flowName"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-breadcrumbs-el :label="flowNames1" />
|
||||||
|
</q-breadcrumbs>
|
||||||
|
<q-separator vertical class="q-mr-xs" />
|
||||||
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
class="q-py-sm"
|
||||||
|
padding="none md none sm"
|
||||||
|
color="blue-1"
|
||||||
|
text-color="primary"
|
||||||
|
size="md"
|
||||||
|
@click="drawerLeft = !drawerLeft"
|
||||||
|
label="変 更"
|
||||||
|
icon="expand_more"
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-space />
|
||||||
|
<q-btn
|
||||||
|
class="q-px-sm q-mr-sm"
|
||||||
|
color="white"
|
||||||
|
size="sm"
|
||||||
|
text-color="black"
|
||||||
|
label="キャンセル"
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
class="q-px-sm"
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
label="保存する"
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-layout
|
||||||
|
container
|
||||||
|
style="height: 91.5dvb"
|
||||||
|
class="shadow-2 rounded-borders"
|
||||||
|
>
|
||||||
|
<q-drawer side="left" overlay bordered v-model="drawerLeft">
|
||||||
|
<div class="q-pa-sm fixed-right">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
color="primary"
|
||||||
|
icon="close"
|
||||||
|
@click="drawerLeft = !drawerLeft"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="q-mt-lg q-pa-sm">
|
||||||
|
<q-card-section>
|
||||||
|
<div class="flex-center">
|
||||||
|
<div class="row q-pl-md">
|
||||||
|
<p class="text-h6">アクション選択</p>
|
||||||
|
</div>
|
||||||
|
<ItemSelector :actName="actName" />
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</div>
|
||||||
|
<q-separator />
|
||||||
|
<div class="q-mt-md q-pa-sm">
|
||||||
|
<q-card-section>
|
||||||
|
<p class="text-h6 q-pl-md q-mb-none">フロー選択</p>
|
||||||
|
<ControlPanel />
|
||||||
|
</q-card-section>
|
||||||
|
</div>
|
||||||
|
<q-separator />
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<div class="q-pa-sm">
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
color="primary"
|
||||||
|
size="md"
|
||||||
|
@click="drawerLeft = !drawerLeft"
|
||||||
|
label="ジャンプ"
|
||||||
|
dense
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-drawer>
|
||||||
|
|
||||||
|
<FlowChartTest />
|
||||||
|
</q-layout>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import FlowChartTest from 'pages/FlowChartTest.vue';
|
||||||
|
import ControlPanel from 'components/flowEditor/left/ControlPanelC.vue';
|
||||||
|
import ItemSelector from 'components/flowEditor/left/ItemSelector.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { storeToRefs } from 'pinia';
|
||||||
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
|
|
||||||
|
const actName = ref('勤怠管理 - 4');
|
||||||
|
const flowNames = ref(['レコードを追加画面', '保存をクリックした時']);
|
||||||
|
|
||||||
|
const drawerLeft = ref(false);
|
||||||
|
const store = useFlowEditorStore();
|
||||||
|
const { flowNames1 } = storeToRefs(store);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass"></style>
|
||||||
112
frontend/src/pages/LoginPage.vue
Normal file
112
frontend/src/pages/LoginPage.vue
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<template>
|
||||||
|
<q-layout view="lHh Lpr fff">
|
||||||
|
<q-page-container>
|
||||||
|
<q-page class="window-height window-width row justify-center items-center">
|
||||||
|
<div class="column q-pa-lg">
|
||||||
|
<div class="row">
|
||||||
|
<q-card :square="false" class="shadow-24" style="width:400px;height:540px;">
|
||||||
|
<q-card-section class="bg-primary">
|
||||||
|
<h4 class="text-h5 text-white q-my-md">{{ title}}</h4>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section>
|
||||||
|
<q-form class="q-px-sm q-pt-xl" ref="loginForm">
|
||||||
|
<q-input square clearable v-model="email" type="email" lazy-rules
|
||||||
|
:rules="[required,isEmail,short]" label="メール">
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon name="email" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<q-input square clearable v-model="password" :type="passwordFieldType" lazy-rules
|
||||||
|
:rules="[required, short]" label="パスワード">
|
||||||
|
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon name="lock" />
|
||||||
|
</template>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon :name="visibilityIcon" @click="switchVisibility" class="cursor-pointer" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</q-form>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions class="q-px-lg">
|
||||||
|
<q-btn :loading="loading" unelevated size="lg" color="secondary" @click="submit" class="full-width text-white"
|
||||||
|
label="ログイン" >
|
||||||
|
<template v-slot:loading>
|
||||||
|
<q-spinner class="on-left" />
|
||||||
|
ログイン中...
|
||||||
|
</template>
|
||||||
|
</q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</q-page>
|
||||||
|
</q-page-container>
|
||||||
|
</q-layout>>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useQuasar } from 'quasar'
|
||||||
|
// import { useRouter } from 'vue-router';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
// import { Auth } from '../control/auth'
|
||||||
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const $q = useQuasar()
|
||||||
|
const loginForm = ref(null);
|
||||||
|
const loading = ref(false);
|
||||||
|
let title = ref('ログイン');
|
||||||
|
let email = ref('');
|
||||||
|
let password = ref('');
|
||||||
|
let visibility = ref(false);
|
||||||
|
let passwordFieldType = ref('password');
|
||||||
|
let visibilityIcon = ref('visibility');
|
||||||
|
const required = (val:string) => {
|
||||||
|
return (val && val.length > 0 || '必須項目')
|
||||||
|
}
|
||||||
|
const isEmail = (val:string) => {
|
||||||
|
const emailPattern = /^(?=[a-zA-Z0-9@._%+-]{6,254}$)[a-zA-Z0-9._%+-]{1,64}@(?:[a-zA-Z0-9-]{1,63}\.){1,8}[a-zA-Z]{2,63}$/
|
||||||
|
return (emailPattern.test(val) || '無効なメールアドレス')
|
||||||
|
}
|
||||||
|
const short = (val:string) => {
|
||||||
|
return (val && val.length > 3 || '値が短く過ぎる')
|
||||||
|
}
|
||||||
|
const switchVisibility = () => {
|
||||||
|
visibility.value = !visibility.value
|
||||||
|
passwordFieldType.value = visibility.value ? 'text' : 'password'
|
||||||
|
visibilityIcon.value = visibility.value ? 'visibility_off' : 'visibility'
|
||||||
|
}
|
||||||
|
const submit = async () =>{
|
||||||
|
loading.value=true;
|
||||||
|
try {
|
||||||
|
const result = await authStore.login(email.value,password.value);
|
||||||
|
loading.value=false;
|
||||||
|
if(result){
|
||||||
|
$q.notify({
|
||||||
|
icon: 'done',
|
||||||
|
color: 'positive',
|
||||||
|
message: 'ログイン成功'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$q.notify({
|
||||||
|
icon: 'error',
|
||||||
|
color: 'negative',
|
||||||
|
message: 'ログイン失敗'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
loading.value=false;
|
||||||
|
$q.notify({
|
||||||
|
icon: 'error',
|
||||||
|
color: 'negative',
|
||||||
|
message: 'ログイン失敗'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
@@ -9,41 +9,67 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="min-height: 100vh;">
|
<div style="min-height: 100vh;">
|
||||||
<div class="q-pa-md">
|
<div class="q-pa-md">
|
||||||
<q-btn-dropdown
|
<q-btn-dropdown split color="primary" label="ルール新規作成" size="lg">
|
||||||
split
|
<q-list>
|
||||||
color="primary"
|
<q-item v-for="action in actions" clickable v-close-popup @click="onItemClick" :key="action">
|
||||||
label="ルール新規作成"
|
<q-item-section>
|
||||||
size="lg"
|
<q-item-label>{{ action }}</q-item-label>
|
||||||
>
|
</q-item-section>
|
||||||
<q-list>
|
</q-item>
|
||||||
<q-item
|
</q-list>
|
||||||
v-for="action in actions"
|
</q-btn-dropdown>
|
||||||
clickable v-close-popup
|
</div>
|
||||||
@click="onItemClick"
|
<div class="q-pa-md">
|
||||||
:key="action"
|
<q-select v-model="model" :options="options" label="Standard"/>
|
||||||
>
|
<q-btn :label="model+'選択'" color="primary" @click="showDg()" />
|
||||||
<q-item-section>
|
<show-dialog v-model:visible="show" :name="model" @close="closeDg" width="400px">
|
||||||
<q-item-label>{{ action }}</q-item-label>
|
<template v-if="model=='アプリ'">
|
||||||
</q-item-section>
|
<app-select ref="appDg" :name="model" type="single"></app-select>
|
||||||
</q-item>
|
</template>
|
||||||
</q-list>
|
<template v-if="model=='フィールド'">
|
||||||
</q-btn-dropdown>
|
<field-select ref="appDg" :name="model" type="multiple" :appId="1"></field-select>
|
||||||
|
</template>
|
||||||
|
<template v-if="model=='アクション'">
|
||||||
|
<action-select ref="appDg" :name="model" type="single"></action-select>
|
||||||
|
</template>
|
||||||
|
</show-dialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import ShowDialog from 'components/ShowDialog.vue';
|
||||||
|
import AppSelect from 'components/AppSelect.vue';
|
||||||
|
import FieldSelect from 'components/FieldSelect.vue';
|
||||||
|
import ActionSelect from 'components/ActionSelect.vue';
|
||||||
|
import { ref } from 'vue'
|
||||||
|
let show = ref(false);
|
||||||
|
let appDg = ref();
|
||||||
|
let model = ref('アプリ');
|
||||||
|
let options = ['アプリ','フィールド','アクション']
|
||||||
|
const showDg = () => {
|
||||||
|
show.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const closeDg = (val:string) => {
|
||||||
|
if (val == 'OK') {
|
||||||
|
alert(JSON.stringify(appDg.value.selected))
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
actions:string[];
|
actions: string[];
|
||||||
}
|
}
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
title:"ルールエディター",
|
title: "ルールエディター",
|
||||||
actions:()=>["フィールド制御","一覧画面","その他"]
|
actions: () => ["フィールド制御", "一覧画面", "その他"]
|
||||||
});
|
});
|
||||||
function onItemClick(evt: Event){
|
function onItemClick(evt: Event) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
206
frontend/src/pages/TenantDomain.vue
Normal file
206
frontend/src/pages/TenantDomain.vue
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<q-table title="Treats" :rows="rows" :columns="columns" row-key="id" selection="single" :filter="filter"
|
||||||
|
:loading="loading" v-model:selected="selected">
|
||||||
|
|
||||||
|
<template v-slot:top>
|
||||||
|
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||||
|
<q-btn class="q-ml-sm" color="primary" :disable="loading" label="編集" @click="editRow" />
|
||||||
|
<q-btn class="q-ml-sm" color="primary" :disable="loading" label="削除" @click="removeRow" />
|
||||||
|
<q-space />
|
||||||
|
<q-input borderless dense debounce="300" color="primary" v-model="filter">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</q-table>
|
||||||
|
|
||||||
|
<q-dialog :model-value="show" persistent>
|
||||||
|
<q-card style="min-width: 400px">
|
||||||
|
<q-card-section>
|
||||||
|
<div class="text-h6">Kintone Account</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-section class="q-pt-none">
|
||||||
|
<q-form class="q-gutter-md">
|
||||||
|
<q-input filled v-model="tenantid" label="Tenant" hint="Tenant ID" lazy-rules
|
||||||
|
:rules="[val => val && val.length > 0 || 'Please type something']" />
|
||||||
|
|
||||||
|
<q-input filled v-model="name" label="Your name *" hint="Kintone envirment name" lazy-rules
|
||||||
|
:rules="[val => val && val.length > 0 || 'Please type something']" />
|
||||||
|
|
||||||
|
<q-input filled type="url" v-model="url" label="Kintone url" hint="Kintone domain address" lazy-rules
|
||||||
|
:rules="[val => val && val.length > 0, isDomain || 'Please type something']" />
|
||||||
|
|
||||||
|
<q-input filled v-model="kintoneuser" label="Login user " hint="Kintone user name" lazy-rules
|
||||||
|
:rules="[val => val && val.length > 0 || 'Please type something']" />
|
||||||
|
|
||||||
|
<q-input v-model="kintonepwd" filled :type="isPwd ? 'password' : 'text'" hint="Password with toggle"
|
||||||
|
label="User password">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer" @click="isPwd = !isPwd" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</q-form>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="right" class="text-primary">
|
||||||
|
<q-btn label="Save" type="submit" color="primary" @click="onSubmit" />
|
||||||
|
<q-btn label="Cancel" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
|
||||||
|
</q-dialog>
|
||||||
|
|
||||||
|
<q-dialog v-model="confirm" persistent>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section class="row items-center">
|
||||||
|
<q-avatar icon="confirm" color="primary" text-color="white" />
|
||||||
|
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
||||||
|
<q-btn flat label="OK" color="primary" v-close-popup @click="deleteDomain()" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, reactive } from 'vue';
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ name: 'id' },
|
||||||
|
{
|
||||||
|
name: 'tenantid',
|
||||||
|
required: true,
|
||||||
|
label: 'Tenant',
|
||||||
|
align: 'left',
|
||||||
|
field: row => row.tenantid,
|
||||||
|
format: val => `${val}`,
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{ name: 'name', align: 'center', label: 'Name', field: 'name', sortable: true },
|
||||||
|
{ name: 'url', align: 'left', label: 'URL', field: 'url', sortable: true },
|
||||||
|
{ name: 'user', label: 'Account', field: 'user' },
|
||||||
|
{ name: 'password', label: 'Password', field: 'password' }
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const filter = ref('');
|
||||||
|
const rows = ref([]);
|
||||||
|
const show = ref(false);
|
||||||
|
const confirm = ref(false);
|
||||||
|
const selected = ref([]);
|
||||||
|
const tenantid = ref('');
|
||||||
|
const name = ref('');
|
||||||
|
const url = ref('');
|
||||||
|
const isPwd = ref(true);
|
||||||
|
const kintoneuser = ref('');
|
||||||
|
const kintonepwd = ref('');
|
||||||
|
|
||||||
|
let editId = ref(0);
|
||||||
|
|
||||||
|
const getDomain = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const result= await api.get(`api/domains/1`);
|
||||||
|
rows.value= result.data.map((item)=>{
|
||||||
|
return { id: item.id, tenantid: item.tenantid, name: item.name, url: item.url, user: item.kintoneuser, password: item.kintonepwd }
|
||||||
|
});
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getDomain();
|
||||||
|
})
|
||||||
|
|
||||||
|
// emulate fetching data from server
|
||||||
|
const addRow = () => {
|
||||||
|
editId.value
|
||||||
|
show.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeRow = () => {
|
||||||
|
//loading.value = true
|
||||||
|
confirm.value = true;
|
||||||
|
let row = JSON.parse(JSON.stringify(selected.value[0]));
|
||||||
|
if (selected.value.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editId.value = row.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteDomain = () => {
|
||||||
|
api.delete(`api/domain/${editId.value}`).then(() => {
|
||||||
|
getDomain();
|
||||||
|
})
|
||||||
|
editId.value = 0;
|
||||||
|
selected.value = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const editRow = () => {
|
||||||
|
if (selected.value.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let row = JSON.parse(JSON.stringify(selected.value[0]));
|
||||||
|
editId.value = row.id;
|
||||||
|
tenantid.value = row.tenantid;
|
||||||
|
name.value = row.name;
|
||||||
|
url.value = row.url;
|
||||||
|
kintoneuser.value = row.user;
|
||||||
|
kintonepwd.value = row.password;
|
||||||
|
isPwd.value = true;
|
||||||
|
show.value = true;
|
||||||
|
};
|
||||||
|
const closeDg = () => {
|
||||||
|
show.value = false;
|
||||||
|
onReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
if (editId.value !== 0) {
|
||||||
|
api.put(`api/domain`, {
|
||||||
|
'id': editId.value,
|
||||||
|
'tenantid': tenantid.value,
|
||||||
|
'name': name.value,
|
||||||
|
'url': url.value,
|
||||||
|
'kintoneuser': kintoneuser.value,
|
||||||
|
'kintonepwd': kintonepwd.value
|
||||||
|
}).then(() => {
|
||||||
|
getDomain();
|
||||||
|
closeDg();
|
||||||
|
onReset();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
api.post(`api/domain`, {
|
||||||
|
'id': 0,
|
||||||
|
'tenantid': tenantid.value,
|
||||||
|
'name': name.value,
|
||||||
|
'url': url.value,
|
||||||
|
'kintoneuser': kintoneuser.value,
|
||||||
|
'kintonepwd': kintonepwd.value
|
||||||
|
}).then(() => {
|
||||||
|
getDomain();
|
||||||
|
closeDg();
|
||||||
|
onReset();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
selected.value = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const onReset = () => {
|
||||||
|
name.value = '';
|
||||||
|
url.value = '';
|
||||||
|
kintoneuser.value = '';
|
||||||
|
kintonepwd.value = '';
|
||||||
|
isPwd.value = true;
|
||||||
|
editId.value = 0;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
270
frontend/src/pages/UserDomain.vue
Normal file
270
frontend/src/pages/UserDomain.vue
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
<!-- <template>
|
||||||
|
<div class="q-pa-md" style="max-width: 400px">
|
||||||
|
|
||||||
|
<q-form
|
||||||
|
@submit="onSubmit"
|
||||||
|
@reset="onReset"
|
||||||
|
class="q-gutter-md"
|
||||||
|
>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="name"
|
||||||
|
label="Your name *"
|
||||||
|
hint="Kintone envirment name"
|
||||||
|
lazy-rules
|
||||||
|
:rules="[ val => val && val.length > 0 || 'Please type something']"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
filled type="url"
|
||||||
|
v-model="url"
|
||||||
|
label="Kintone url"
|
||||||
|
hint="Kintone domain address"
|
||||||
|
lazy-rules
|
||||||
|
:rules="[ val => val && val.length > 0,isDomain || 'Please type something']"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="username"
|
||||||
|
label="Login user "
|
||||||
|
hint="Kintone user name"
|
||||||
|
lazy-rules
|
||||||
|
:rules="[ val => val && val.length > 0 || 'Please type something']"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-input v-model="password" filled :type="isPwd ? 'password' : 'text'" hint="Password with toggle" label="User password">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
:name="isPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<q-toggle v-model="accept" label="Active Domain" />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<q-btn label="Submit" type="submit" color="primary"/>
|
||||||
|
<q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" />
|
||||||
|
</div>
|
||||||
|
</q-form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { useQuasar } from 'quasar'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setup () {
|
||||||
|
const $q = useQuasar()
|
||||||
|
|
||||||
|
const name = ref(null)
|
||||||
|
const age = ref(null)
|
||||||
|
const accept = ref(false)
|
||||||
|
const isPwd =ref(true)
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
age,
|
||||||
|
accept,
|
||||||
|
isPwd,
|
||||||
|
isDomain(val) {
|
||||||
|
const domainPattern = /^https?\/\/:([a-zA-Z] +\.){1}([a-zA-Z]+)\.([a-zA-Z]+)$/;
|
||||||
|
return (domainPattern.test(val) || '無効なURL')
|
||||||
|
},
|
||||||
|
|
||||||
|
onSubmit () {
|
||||||
|
if (accept.value !== true) {
|
||||||
|
$q.notify({
|
||||||
|
color: 'red-5',
|
||||||
|
textColor: 'white',
|
||||||
|
icon: 'warning',
|
||||||
|
message: 'You need to accept the license and terms first'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$q.notify({
|
||||||
|
color: 'green-4',
|
||||||
|
textColor: 'white',
|
||||||
|
icon: 'cloud_done',
|
||||||
|
message: 'Submitted'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onReset () {
|
||||||
|
name.value = null
|
||||||
|
age.value = null
|
||||||
|
accept.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script> -->
|
||||||
|
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<q-table grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" v-model:selected="selected" row-key="name" :filter="filter" hide-header>
|
||||||
|
<template v-slot:top>
|
||||||
|
<div class="q-pa-md q-gutter-sm">
|
||||||
|
<q-btn color="primary" label="追加" @click="newDomain()" dense />
|
||||||
|
</div>
|
||||||
|
<q-space />
|
||||||
|
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
<template v-slot:item="props">
|
||||||
|
<div class="q-pa-xs col-xs-12 col-sm-6 col-md-4">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="q-table__grid-item-row">
|
||||||
|
<div class="q-table__grid-item-title">Domain</div>
|
||||||
|
<div class="q-table__grid-item-value">{{ props.row.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="q-table__grid-item-row">
|
||||||
|
<div class="q-table__grid-item-title">URL</div>
|
||||||
|
<div class="q-table__grid-item-value">{{ props.row.url }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="q-table__grid-item-row">
|
||||||
|
<div class="q-table__grid-item-title">Account</div>
|
||||||
|
<div class="q-table__grid-item-value">{{ props.row.kintoneuser }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="q-table__grid-item-row">
|
||||||
|
<div class="q-table__grid-item-value">{{isActive(props.row.id) }}</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
<q-separator />
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn flat @click = "activeDomain(props.row.id)">有効</q-btn>
|
||||||
|
<q-btn flat @click = "deleteConfirm(props.row)">削除</q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
|
||||||
|
<show-dialog v-model:visible="show" name="ドメイン" @close="closeDg" width="350px">
|
||||||
|
<domain-select ref="domainDg" name="ドメイン" type="multiple"></domain-select>
|
||||||
|
</show-dialog>
|
||||||
|
|
||||||
|
<q-dialog v-model="confirm" persistent>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section class="row items-center">
|
||||||
|
<q-avatar icon="confirm" color="primary" text-color="white" />
|
||||||
|
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
||||||
|
<q-btn flat label="OK" color="primary" v-close-popup @click = "deleteDomain()"/>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useQuasar } from 'quasar'
|
||||||
|
import { ref, onMounted, reactive } from 'vue'
|
||||||
|
import ShowDialog from 'components/ShowDialog.vue';
|
||||||
|
import DomainSelect from 'components/DomainSelect.vue';
|
||||||
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
import { domain } from 'process';
|
||||||
|
|
||||||
|
const $q = useQuasar()
|
||||||
|
|
||||||
|
const domainDg = ref();
|
||||||
|
const selected = ref([])
|
||||||
|
|
||||||
|
const show = ref(false);
|
||||||
|
const confirm = ref(false)
|
||||||
|
|
||||||
|
let editId = ref(0);
|
||||||
|
let activedomainid = ref(0);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ name: 'id'},
|
||||||
|
{name: 'name',required: true,label: 'Name',align: 'left',field: 'name',sortable: true},
|
||||||
|
{ name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true },
|
||||||
|
{ name: 'kintoneuser', label: 'User', field: 'kintoneuser', sortable: true },
|
||||||
|
{ name: 'kintonepwd' },
|
||||||
|
{ name: 'active', field: 'active'}
|
||||||
|
]
|
||||||
|
|
||||||
|
const rows = ref([] as any[]);
|
||||||
|
|
||||||
|
const isActive = (id:number) =>{
|
||||||
|
if(id == activedomainid.value)
|
||||||
|
return "Active";
|
||||||
|
else
|
||||||
|
return "Inactive";
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDomain = () => {
|
||||||
|
editId.value = 0;
|
||||||
|
show.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const activeDomain = (id:number) => {
|
||||||
|
api.put(`api/activedomain/`+ id).then(() =>{
|
||||||
|
getDomain();
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteConfirm = (row:object) => {
|
||||||
|
confirm.value = true;
|
||||||
|
editId.value = row.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteDomain = () => {
|
||||||
|
api.delete(`api/domain/`+ editId.value+'/1').then(() =>{
|
||||||
|
getDomain();
|
||||||
|
})
|
||||||
|
editId.value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDg = (val:string) => {
|
||||||
|
if (val == 'OK') {
|
||||||
|
let dodmainids =[];
|
||||||
|
let domains = JSON.parse(JSON.stringify(domainDg.value.selected));
|
||||||
|
for(var key in domains)
|
||||||
|
{
|
||||||
|
dodmainids.push(domains[key].id);
|
||||||
|
}
|
||||||
|
api.post(`api/domain`, dodmainids).then(() =>{getDomain();});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
const getDomain = async () => {
|
||||||
|
const resp = await api.get(`api/activedomain`);
|
||||||
|
activedomainid.value = resp.data.id;
|
||||||
|
const domainResult = await api.get(`api/domain`);
|
||||||
|
const domains = domainResult.data as any[];
|
||||||
|
rows.value=domains.map((item)=>{
|
||||||
|
return { id:item.id,name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
onMounted(async () => {
|
||||||
|
await getDomain();
|
||||||
|
})
|
||||||
|
|
||||||
|
const isDomain = (val) =>{
|
||||||
|
// const domainPattern = /^https\/\/:([a-zA-Z] +\.){1}([a-zA-Z]+)\.([a-zA-Z]+)$/;
|
||||||
|
// return (domainPattern.test(val) || '無効なURL')
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
39
frontend/src/pages/conditionPage.vue
Normal file
39
frontend/src/pages/conditionPage.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<q-page>
|
||||||
|
<div class="flowchart">
|
||||||
|
<q-btn @click="showCondition()" class="q-mt-md" color="primary" icon="mdi-plus">条件エディタ表示</q-btn>
|
||||||
|
</div>
|
||||||
|
<condition-editor v-model:show="show" v-model:conditionTree="tree"></condition-editor>
|
||||||
|
<q-code>{{conditionString}}</q-code>
|
||||||
|
</q-page>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {ref,reactive,computed} from 'vue';
|
||||||
|
import ConditionEditor from '../components/ConditionEditor/ConditionEditor.vue';
|
||||||
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
|
import { ConditionTree,GroupNode,ConditionNode,LogicalOperator,Operator } from 'app/src/types/Conditions';
|
||||||
|
|
||||||
|
const store = useFlowEditorStore();
|
||||||
|
const tree = reactive(new ConditionTree());
|
||||||
|
const newNode = new ConditionNode({},Operator.Equal,'',tree.root);
|
||||||
|
tree.addNode(tree.root,newNode);
|
||||||
|
|
||||||
|
const show =ref(false);
|
||||||
|
const showCondition=()=>{
|
||||||
|
show.value=true;
|
||||||
|
}
|
||||||
|
const conditionString = computed(()=>{
|
||||||
|
return tree.buildConditionString(tree.root);
|
||||||
|
});
|
||||||
|
store.setApp({
|
||||||
|
appId:'146',
|
||||||
|
name:'トリトン管理部日報'
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.flowchart {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
96
frontend/src/pages/testFlow.vue
Normal file
96
frontend/src/pages/testFlow.vue
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-pa-md q-gutter-sm">
|
||||||
|
<q-btn label="プロパティ" icon="keyboard_arrow_right" color="primary" @click="open('right')" />
|
||||||
|
<!-- <q-btn label="Readプロパティ" icon="keyboard_arrow_right" color="primary" @click="write('right')" /> -->
|
||||||
|
|
||||||
|
<q-dialog v-model="dialog" :position="position">
|
||||||
|
<q-card class="column full-height" style="width: 300px">
|
||||||
|
<q-card-section>
|
||||||
|
<div class="text-h6">プロパティ</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
|
||||||
|
<q-card-section class="col q-pt-none">
|
||||||
|
<ActionProperty :jsonData="jsonData" :jsonValue="jsonValue"/>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions align="right" class="bg-white text-teal">
|
||||||
|
<q-btn flat label="Save" v-close-popup @click="save"/>
|
||||||
|
<q-btn flat label="Cancel" v-close-popup />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref,onMounted } from 'vue'
|
||||||
|
import ActionProperty from 'components/right/ActionProperty.vue';
|
||||||
|
const dialog = ref(false)
|
||||||
|
const position = ref('top')
|
||||||
|
|
||||||
|
const jsonData = {
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
component: 'InputText',
|
||||||
|
props: {
|
||||||
|
name:'1',
|
||||||
|
placeholder: 'Enter some text',
|
||||||
|
modelValue: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'SelectBox',
|
||||||
|
props: {
|
||||||
|
name:'2',
|
||||||
|
placeholder: 'Choose an option',
|
||||||
|
modelValue: '',
|
||||||
|
options: [
|
||||||
|
'option1',
|
||||||
|
'option2',
|
||||||
|
'option3'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'DatePicker',
|
||||||
|
props: {
|
||||||
|
name:'3',
|
||||||
|
placeholder: 'Choose a date',
|
||||||
|
modelValue: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'FieldInput',
|
||||||
|
props: {
|
||||||
|
name:'4',
|
||||||
|
placeholder: 'Choose a field',
|
||||||
|
modelValue: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let jsonValue = {
|
||||||
|
1:'abc',
|
||||||
|
2:'option2',
|
||||||
|
3:'2023/09/04',
|
||||||
|
4:'6666'
|
||||||
|
};
|
||||||
|
|
||||||
|
const open = (pos:string) => {
|
||||||
|
position.value = pos
|
||||||
|
dialog.value = true
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = async () =>{
|
||||||
|
|
||||||
|
jsonData.elements.forEach(property => {
|
||||||
|
if(jsonValue != undefined)
|
||||||
|
{
|
||||||
|
jsonValue[property.props.name] = property.props.modelValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(jsonValue);
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -1,13 +1,88 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<q-page>
|
||||||
|
|
||||||
</div>
|
<div class="q-pa-md column content-center items-center">
|
||||||
|
<div>
|
||||||
|
<q-btn label="アクション選択" color="primary" @click="showDg()" v-if="addshow" />
|
||||||
|
<show-dialog v-model:visible="show" name="アクション" @close="closeDg" width="350px">
|
||||||
|
<action-select ref="appDg" name="アクション" type="single"></action-select>
|
||||||
|
</show-dialog>
|
||||||
|
</div>
|
||||||
|
<div id="action"></div>
|
||||||
|
</div>
|
||||||
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
export default {
|
import { ref } from 'vue';
|
||||||
|
import ShowDialog from 'components/ShowDialog.vue';
|
||||||
|
import ActionSelect from 'components/ActionSelect.vue';
|
||||||
|
|
||||||
|
const show = ref(false);
|
||||||
|
const addshow = ref(true);
|
||||||
|
const appDg = ref();
|
||||||
|
|
||||||
|
const showDg = () => {
|
||||||
|
show.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDg = (val: string) => {
|
||||||
|
if (val == 'OK') {
|
||||||
|
//alert(JSON.stringify(appDg.value.selected[0].content));
|
||||||
|
let oDiv1 = document.getElementById('action');
|
||||||
|
let oDiv2 = document.createElement('div');
|
||||||
|
oDiv2.setAttribute('class', 'action');
|
||||||
|
if (oDiv1 !== null) {
|
||||||
|
oDiv2.innerHTML = appDg.value.selected[0].content;
|
||||||
|
oDiv1?.append(oDiv2);
|
||||||
|
// let oAdd = oDiv2.getElementsByClassName('next')[0];
|
||||||
|
// oAdd.addEventListener('mouseenter', mouseenter);
|
||||||
|
// oAdd.addEventListener('mouseleave', mouseleave);
|
||||||
|
// let oDel = oDiv2.getElementsByClassName('del')[0];
|
||||||
|
// oDel.addEventListener('click', clickdel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementsByClassName('action').length > 0) {
|
||||||
|
addshow.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mouseenter = (event: Event) => {
|
||||||
|
console.log('1');
|
||||||
|
let oAdd = "<div style='display: table-cell;'><button type='button' onclick='clickadd(this.parentElement.parentElement.parentElement.parentElement)'>+</button></div>";
|
||||||
|
let oDiv1 = event.target as Element
|
||||||
|
let oDivs = oDiv1?.getElementsByClassName('add');
|
||||||
|
if (oDivs.length === 0) {
|
||||||
|
let oDiv2 = document.createElement('div');
|
||||||
|
oDiv2.className = "add";
|
||||||
|
oDiv2.setAttribute("style", "display:table-row;height:inherit;position: absolute;left:calc(50% - 19px);");
|
||||||
|
oDiv2.innerHTML = oAdd;
|
||||||
|
oDiv1?.append(oDiv2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mouseleave = (event: Event) => {
|
||||||
|
console.log('2');
|
||||||
|
let oDiv2 = (event.target as Element)?.parentElement?.getElementsByClassName('add');
|
||||||
|
oDiv2[0]?.remove();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const clickadd = (pdiv: Element) => {
|
||||||
|
console.log('3');
|
||||||
|
show.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clickdel = (pdiv: Element) => {
|
||||||
|
pdiv.remove();
|
||||||
|
if (document.getElementsByClassName('action').length == 0) {
|
||||||
|
addshow.value = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.clickadd = clickadd;
|
||||||
|
window.clickdel = clickdel;
|
||||||
|
window.mouseenter = mouseenter;
|
||||||
|
window.mouseleave = mouseleave;
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
101
frontend/src/pages/testRight.vue
Normal file
101
frontend/src/pages/testRight.vue
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-pa-md q-gutter-sm">
|
||||||
|
<q-btn label="プロパティ" icon="keyboard_arrow_right" color="primary" @click="drawerRight = !drawerRight" />
|
||||||
|
<!-- <q-btn label="Readプロパティ" icon="keyboard_arrow_right" color="primary" @click="write('right')" /> -->
|
||||||
|
<q-drawer
|
||||||
|
side="right"
|
||||||
|
v-model="drawerRight"
|
||||||
|
show-if-above
|
||||||
|
bordered
|
||||||
|
:width="301"
|
||||||
|
:breakpoint="500"
|
||||||
|
class="bg-grey-3"
|
||||||
|
>
|
||||||
|
<q-card class="column full-height" style="width: 300px">
|
||||||
|
<q-card-section>
|
||||||
|
<div class="text-h6">プロパティ</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
|
||||||
|
<q-card-section class="col q-pt-none">
|
||||||
|
<ActionProperty :jsonData="jsonData" :jsonValue="jsonValue" v-if="drawerRight"/>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions align="right" class="bg-white text-teal">
|
||||||
|
<q-btn flat label="Save" @click="save"/>
|
||||||
|
<q-btn flat label="Cancel" @click="cancel" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-drawer>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { reactive, ref } from 'vue'
|
||||||
|
import ActionProperty from 'components/right/ActionProperty.vue';
|
||||||
|
const drawerRight = ref(false);
|
||||||
|
const jsonData = {
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
component: 'InputText',
|
||||||
|
props: {
|
||||||
|
name:'1',
|
||||||
|
placeholder: 'Enter some text',
|
||||||
|
modelValue: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'SelectBox',
|
||||||
|
props: {
|
||||||
|
name:'2',
|
||||||
|
placeholder: 'Choose an option',
|
||||||
|
modelValue: '',
|
||||||
|
options: [
|
||||||
|
'option1',
|
||||||
|
'option2',
|
||||||
|
'option3'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'DatePicker',
|
||||||
|
props: {
|
||||||
|
name:'3',
|
||||||
|
placeholder: 'Choose a date',
|
||||||
|
modelValue: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: 'FieldInput',
|
||||||
|
props: {
|
||||||
|
name:'4',
|
||||||
|
placeholder: 'Choose a field',
|
||||||
|
modelValue: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
let jsonValue = {
|
||||||
|
1:'abc',
|
||||||
|
2:'option2',
|
||||||
|
3:'2023/09/04',
|
||||||
|
4:'6666'
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancel = async() =>{
|
||||||
|
drawerRight.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const save = async () =>{
|
||||||
|
|
||||||
|
jsonData.elements.forEach(property => {
|
||||||
|
if(jsonValue != undefined)
|
||||||
|
{
|
||||||
|
jsonValue[property.props.name] = property.props.modelValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(jsonValue);
|
||||||
|
drawerRight.value=false;
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
} from 'vue-router';
|
} from 'vue-router';
|
||||||
|
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
/*
|
/*
|
||||||
* If not building with SSR mode, you can
|
* If not building with SSR mode, you can
|
||||||
* directly export the Router instantiation;
|
* directly export the Router instantiation;
|
||||||
@@ -17,20 +17,60 @@ import routes from './routes';
|
|||||||
* with the Router instance.
|
* with the Router instance.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default route(function (/* { store, ssrContext } */) {
|
const createHistory = process.env.SERVER
|
||||||
const createHistory = process.env.SERVER
|
? createMemoryHistory
|
||||||
? createMemoryHistory
|
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
|
||||||
: (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
|
|
||||||
|
|
||||||
const Router = createRouter({
|
const routerInstance = createRouter({
|
||||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
routes,
|
routes,
|
||||||
|
|
||||||
// Leave this as is and make changes in quasar.conf.js instead!
|
// Leave this as is and make changes in quasar.conf.js instead!
|
||||||
// quasar.conf.js -> build -> vueRouterMode
|
// quasar.conf.js -> build -> vueRouterMode
|
||||||
// quasar.conf.js -> build -> publicPath
|
// quasar.conf.js -> build -> publicPath
|
||||||
history: createHistory(process.env.VUE_ROUTER_BASE),
|
history: createHistory(process.env.VUE_ROUTER_BASE),
|
||||||
});
|
|
||||||
|
|
||||||
return Router;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default route(function (/* { store, ssrContext } */) {
|
||||||
|
|
||||||
|
routerInstance.beforeEach(async (to) => {
|
||||||
|
// clear alert on route change
|
||||||
|
//const alertStore = useAlertStore();
|
||||||
|
//alertStore.clear();
|
||||||
|
|
||||||
|
// redirect to login page if not logged in and trying to access a restricted page
|
||||||
|
const publicPages = ['/login'];
|
||||||
|
const authRequired = !publicPages.includes(to.path);
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
if (authRequired && !authStore.token) {
|
||||||
|
authStore.returnUrl = to.fullPath;
|
||||||
|
return '/login';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return routerInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const router = routerInstance;
|
||||||
|
|
||||||
|
|
||||||
|
// const createHistory = process.env.SERVER
|
||||||
|
// ? createMemoryHistory
|
||||||
|
// : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory);
|
||||||
|
|
||||||
|
// export const Router = createRouter({
|
||||||
|
// scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
|
// routes,
|
||||||
|
|
||||||
|
// // Leave this as is and make changes in quasar.conf.js instead!
|
||||||
|
// // quasar.conf.js -> build -> vueRouterMode
|
||||||
|
// // quasar.conf.js -> build -> publicPath
|
||||||
|
// history: createHistory(process.env.VUE_ROUTER_BASE),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// export default route(function (/* { store, ssrContext } */) {
|
||||||
|
// return Router;
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,33 @@ import { RouteRecordRaw } from 'vue-router';
|
|||||||
|
|
||||||
const routes: RouteRecordRaw[] = [
|
const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/login',
|
||||||
component: () => import('layouts/MainLayout.vue'),
|
component: () => import('pages/LoginPage.vue')
|
||||||
children: [{ path: '', component: () => import('pages/IndexPage.vue') }],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/ruleEditor/',
|
path:'/FlowChart',
|
||||||
component: () => import('layouts/MainLayout.vue'),
|
component:()=>import('layouts/MainLayout.vue'),
|
||||||
children: [{ path: '', component: () => import('pages/RuleEditor.vue') }],
|
children:[
|
||||||
|
{path:'',component:()=>import('pages/FlowChart.vue')}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: () => import('layouts/MainLayout.vue'),
|
||||||
|
children: [
|
||||||
|
{ path: '', component: () => import('pages/IndexPage.vue') },
|
||||||
|
{ path: 'ruleEditor', component: () => import('pages/RuleEditor.vue') },
|
||||||
|
{ path: 'test', component: () => import('pages/testQursar.vue') },
|
||||||
|
{ path: 'flow', component: () => import('pages/testFlow.vue') },
|
||||||
|
{ path: 'FlowChartTest', component: () => import('pages/FlowChartTest.vue') },
|
||||||
|
{ path: 'flowEditor', component: () => import('pages/FlowEditorPage.vue') },
|
||||||
|
// { path: 'FlowChart', component: () => import('pages/FlowChart.vue') },
|
||||||
|
{ path: 'right', component: () => import('pages/testRight.vue') },
|
||||||
|
{ path: 'domain', component: () => import('pages/TenantDomain.vue') },
|
||||||
|
{ path: 'userdomain', component: () => import('pages/UserDomain.vue')},
|
||||||
|
{ path: 'condition', component: () => import('pages/conditionPage.vue') }
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
// Always leave this as last one,
|
// Always leave this as last one,
|
||||||
// but you can also remove it
|
// but you can also remove it
|
||||||
{
|
{
|
||||||
|
|||||||
118
frontend/src/stores/flowEditor.ts
Normal file
118
frontend/src/stores/flowEditor.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { AppInfo ,IActionFlow, IActionNode} from 'src/types/ActionTypes';
|
||||||
|
import { IKintoneEvent,KintoneEventManager } from 'src/types/KintoneEvents';
|
||||||
|
import {FlowCtrl } from '../control/flowctrl';
|
||||||
|
|
||||||
|
export interface FlowEditorState{
|
||||||
|
flowNames1:string;
|
||||||
|
appInfo?:AppInfo;
|
||||||
|
flows?:IActionFlow[];
|
||||||
|
selectedFlow?:IActionFlow|undefined;
|
||||||
|
activeNode:IActionNode|undefined;
|
||||||
|
eventTree:KintoneEventManager;
|
||||||
|
selectedEvent:IKintoneEvent|undefined;
|
||||||
|
expandedScreen:any[];
|
||||||
|
}
|
||||||
|
const flowCtrl=new FlowCtrl();
|
||||||
|
const eventTree = new KintoneEventManager();
|
||||||
|
export const useFlowEditorStore = defineStore("flowEditor",{
|
||||||
|
state: ():FlowEditorState => ({
|
||||||
|
flowNames1: '',
|
||||||
|
appInfo:undefined,
|
||||||
|
flows:[],
|
||||||
|
selectedFlow:undefined,
|
||||||
|
activeNode:undefined,
|
||||||
|
eventTree:eventTree,
|
||||||
|
selectedEvent:undefined,
|
||||||
|
expandedScreen:[]
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns 現在編集しているフロー
|
||||||
|
*/
|
||||||
|
currentFlow():IActionFlow|undefined{
|
||||||
|
return this.selectedFlow;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* KintoneイベントIDから、バンドしているフローを検索する
|
||||||
|
* @param state
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
findFlowByEventId(state){
|
||||||
|
return (eventId:string)=>{
|
||||||
|
return state.flows?.find((flow)=>{
|
||||||
|
const root=flow.getRoot();
|
||||||
|
return root?.name===eventId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
setFlows(flows:IActionFlow[]){
|
||||||
|
this.flows=flows;
|
||||||
|
},
|
||||||
|
selectFlow(flow:IActionFlow){
|
||||||
|
this.selectedFlow=flow;
|
||||||
|
},
|
||||||
|
setActiveNode(node:IActionNode){
|
||||||
|
this.activeNode=node;
|
||||||
|
},
|
||||||
|
setApp(app:AppInfo){
|
||||||
|
this.appInfo=app;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* DBからフルーを保存する
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async loadFlow(){
|
||||||
|
if(this.appInfo===undefined) return;
|
||||||
|
const actionFlows = await flowCtrl.getFlows(this.appInfo?.appId);
|
||||||
|
//eventTreeにバンドする
|
||||||
|
this.eventTree.bindFlows(actionFlows);
|
||||||
|
if(actionFlows===undefined || actionFlows.length===0){
|
||||||
|
this.flows=[];
|
||||||
|
this.selectedFlow=undefined;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setFlows(actionFlows);
|
||||||
|
if(actionFlows && actionFlows.length>0){
|
||||||
|
this.selectFlow(actionFlows[0]);
|
||||||
|
}
|
||||||
|
const expandNames = actionFlows.map(flow=>flow.getRoot()?.title);
|
||||||
|
// const expandName =actionFlows[0].getRoot()?.title;
|
||||||
|
this.expandedScreen=expandNames;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* フローをDBに保存及び更新する
|
||||||
|
*/
|
||||||
|
async saveFlow(flow:IActionFlow){
|
||||||
|
const root=flow.getRoot();
|
||||||
|
const isNew = flow.id==='';
|
||||||
|
const jsonData={
|
||||||
|
flowid: isNew ? flow.createNewId():flow.id,
|
||||||
|
appid: this.appInfo?.appId,
|
||||||
|
eventid: root?.name,
|
||||||
|
name: root?.subTitle,
|
||||||
|
content: JSON.stringify(flow)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isNew){
|
||||||
|
return await flowCtrl.SaveFlow(jsonData);
|
||||||
|
}else{
|
||||||
|
return await flowCtrl.UpdateFlow(jsonData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* デプロイする
|
||||||
|
*/
|
||||||
|
async deploy():Promise<boolean>{
|
||||||
|
if(this.appInfo===undefined){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return await flowCtrl.depoly(this.appInfo?.appId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
32
frontend/src/stores/index.ts
Normal file
32
frontend/src/stores/index.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { store } from 'quasar/wrappers'
|
||||||
|
import { createPinia } from 'pinia'
|
||||||
|
import { Router } from 'vue-router';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When adding new properties to stores, you should also
|
||||||
|
* extend the `PiniaCustomProperties` interface.
|
||||||
|
* @see https://pinia.vuejs.org/core-concepts/plugins.html#typing-new-store-properties
|
||||||
|
*/
|
||||||
|
declare module 'pinia' {
|
||||||
|
export interface PiniaCustomProperties {
|
||||||
|
readonly router: Router;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If not building with SSR mode, you can
|
||||||
|
* directly export the Store instantiation;
|
||||||
|
*
|
||||||
|
* The function below can be async too; either use
|
||||||
|
* async/await or return a Promise which resolves
|
||||||
|
* with the Store instance.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default store((/* { ssrContext } */) => {
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
// You can add Pinia plugins here
|
||||||
|
// pinia.use(SomePiniaPlugin)
|
||||||
|
|
||||||
|
return pinia
|
||||||
|
})
|
||||||
10
frontend/src/stores/store-flag.d.ts
vendored
Normal file
10
frontend/src/stores/store-flag.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
|
||||||
|
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
|
||||||
|
import "quasar/dist/types/feature-flag";
|
||||||
|
|
||||||
|
declare module "quasar/dist/types/feature-flag" {
|
||||||
|
interface QuasarFeatureFlags {
|
||||||
|
store: true;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
frontend/src/stores/testStore.ts
Normal file
19
frontend/src/stores/testStore.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
|
||||||
|
export const useCounterStore = defineStore('counter', {
|
||||||
|
state: () => ({
|
||||||
|
counter: 0
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
doubleCount (state) {
|
||||||
|
return state.counter * 2;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
increment () {
|
||||||
|
this.counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
91
frontend/src/stores/useAuthStore.ts
Normal file
91
frontend/src/stores/useAuthStore.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
import {router} from 'src/router';
|
||||||
|
import {IDomainInfo} from '../types/ActionTypes';
|
||||||
|
|
||||||
|
|
||||||
|
export interface IUserState{
|
||||||
|
token?:string;
|
||||||
|
returnUrl:string;
|
||||||
|
currentDomain:IDomainInfo;
|
||||||
|
LeftDrawer:boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore({
|
||||||
|
id: 'auth',
|
||||||
|
state: ():IUserState =>{
|
||||||
|
const token=localStorage.getItem('token')||'';
|
||||||
|
if(token!==''){
|
||||||
|
api.defaults.headers["Authorization"]='Bearer ' + token;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
returnUrl: '',
|
||||||
|
LeftDrawer:false,
|
||||||
|
currentDomain: JSON.parse(localStorage.getItem('currentDomain')||"{}")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters:{
|
||||||
|
toggleLeftDrawer():boolean{
|
||||||
|
return this.LeftDrawer;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
toggleLeftMenu(){
|
||||||
|
this.LeftDrawer=!this.LeftDrawer;
|
||||||
|
},
|
||||||
|
async login(username:string, password:string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('username', username);
|
||||||
|
params.append('password', password);
|
||||||
|
try{
|
||||||
|
const result = await api.post(`api/token`,params);
|
||||||
|
console.info(result);
|
||||||
|
this.token =result.data.access_token;
|
||||||
|
localStorage.setItem('token', result.data.access_token);
|
||||||
|
api.defaults.headers["Authorization"]='Bearer ' + this.token;
|
||||||
|
this.currentDomain=await this.getCurrentDomain();
|
||||||
|
localStorage.setItem('currentDomain',JSON.stringify(this.currentDomain));
|
||||||
|
this.router.push(this.returnUrl || '/');
|
||||||
|
return true;
|
||||||
|
}catch(e)
|
||||||
|
{
|
||||||
|
console.info(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getCurrentDomain():Promise<IDomainInfo>{
|
||||||
|
const activedomain = await api.get(`api/activedomain`);
|
||||||
|
return {
|
||||||
|
id:activedomain.data.id,
|
||||||
|
domainName:activedomain.data.name,
|
||||||
|
kintoneUrl:activedomain.data.url
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async getUserDomains():Promise<IDomainInfo[]>{
|
||||||
|
const resp = await api.get(`api/domain`);
|
||||||
|
const domains =resp.data as any[];
|
||||||
|
return domains.map(data=>{
|
||||||
|
return {
|
||||||
|
id:data.id,
|
||||||
|
domainName:data.name,
|
||||||
|
kintoneUrl:data.url
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
logout() {
|
||||||
|
this.token = undefined;
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
localStorage.removeItem('currentDomain');
|
||||||
|
router.push('/login');
|
||||||
|
},
|
||||||
|
async setCurrentDomain(domain:IDomainInfo){
|
||||||
|
if(domain.id===this.currentDomain.id){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await api.put(`api/activedomain/${domain.id}`);
|
||||||
|
this.currentDomain=domain;
|
||||||
|
localStorage.setItem('currentDomain',JSON.stringify(this.currentDomain));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
520
frontend/src/types/ActionTypes.ts
Normal file
520
frontend/src/types/ActionTypes.ts
Normal file
@@ -0,0 +1,520 @@
|
|||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
export interface IDomainInfo{
|
||||||
|
id:number;
|
||||||
|
domainName:string;
|
||||||
|
kintoneUrl:string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アプリ情報
|
||||||
|
*/
|
||||||
|
export interface AppInfo {
|
||||||
|
appId: string;
|
||||||
|
code?: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 属性項目情報
|
||||||
|
*/
|
||||||
|
export interface IProp{
|
||||||
|
//プロパティ名
|
||||||
|
name: string;
|
||||||
|
//プロパティ表示名
|
||||||
|
displayName: string;
|
||||||
|
placeholder: string;
|
||||||
|
//入力提示・説明
|
||||||
|
hint:string;
|
||||||
|
//関連属性リスト
|
||||||
|
connectProps:[{key:string,propName:string}]|undefined;
|
||||||
|
//プロパティ設定値
|
||||||
|
modelValue: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アクションのプロパティ定義
|
||||||
|
*/
|
||||||
|
export interface IActionProperty {
|
||||||
|
component: string;
|
||||||
|
props: IProp;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 変数オブジェクト
|
||||||
|
*/
|
||||||
|
export interface IActionVariable{
|
||||||
|
actionName:string;
|
||||||
|
displayName:string;
|
||||||
|
name:string;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* アクションタイプ定義
|
||||||
|
*/
|
||||||
|
export interface IActionNode {
|
||||||
|
id: string;
|
||||||
|
//アクション名
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
varName:IProp|undefined;
|
||||||
|
subTitle: string;
|
||||||
|
inputPoint: string;
|
||||||
|
//出力ポイント(条件分岐以外未使用)
|
||||||
|
outputPoints: Array<string>;
|
||||||
|
//ルートアクション(Kintone event)
|
||||||
|
isRoot: boolean;
|
||||||
|
//アクションのプロパティ定義
|
||||||
|
actionProps: Array<IActionProperty>;
|
||||||
|
//アクションのプロパティ設定値抽出
|
||||||
|
ActionValue: object
|
||||||
|
prevNodeId?: string;
|
||||||
|
nextNodeIds: Map<string, string>;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* アクションフローの定義
|
||||||
|
*/
|
||||||
|
export interface IActionFlow {
|
||||||
|
id: string;
|
||||||
|
actionNodes: IActionNode[];
|
||||||
|
addNode(newNode: IActionNode, prevNode?: IActionNode, inputPoint?: string): IActionNode;
|
||||||
|
removeNode(targetNode: IActionNode): boolean;
|
||||||
|
removeAllNext(targetNodeId: string): void;
|
||||||
|
getVarNames(currentNode:IActionNode):IActionVariable[];
|
||||||
|
findNodeById(id: string): IActionNode | undefined;
|
||||||
|
toJSON(): any;
|
||||||
|
getRoot(): IActionNode | undefined;
|
||||||
|
createNewId(): string;
|
||||||
|
getColumns(node:IActionNode):number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アクションのプロパティ定義に基づいたクラス
|
||||||
|
*/
|
||||||
|
class ActionProperty implements IActionProperty {
|
||||||
|
component: string;
|
||||||
|
props: IProp;
|
||||||
|
|
||||||
|
static defaultProperty(): IActionProperty {
|
||||||
|
return new ActionProperty('InputText', 'displayName', '表示名', '表示を入力してください', '','');
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
component: string,
|
||||||
|
name: string,
|
||||||
|
displayName: string,
|
||||||
|
placeholder: string,
|
||||||
|
hint:string,
|
||||||
|
modelValue: any
|
||||||
|
) {
|
||||||
|
this.component = component;
|
||||||
|
this.props = {
|
||||||
|
name: name,
|
||||||
|
displayName: displayName,
|
||||||
|
placeholder: placeholder,
|
||||||
|
hint:hint,
|
||||||
|
connectProps:undefined,
|
||||||
|
modelValue: modelValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IActionNodeの実装、RootActionNode以外のアクション定義
|
||||||
|
*/
|
||||||
|
export class ActionNode implements IActionNode {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
get title(): string {
|
||||||
|
const prop = this.actionProps.find((prop) => prop.props.name === "displayName");
|
||||||
|
return prop?.props.modelValue;
|
||||||
|
};
|
||||||
|
get subTitle(): string {
|
||||||
|
return this.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
//変数名
|
||||||
|
get varName():IProp|undefined{
|
||||||
|
const prop = this.actionProps.find((prop) => prop.props.name === "verName");
|
||||||
|
return prop?.props;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputPoint: string;
|
||||||
|
//出力ポイント(条件分岐以外未使用)
|
||||||
|
outputPoints: Array<string>;
|
||||||
|
actionProps: Array<IActionProperty>;
|
||||||
|
get isRoot(): boolean {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
get ActionValue(): object {
|
||||||
|
const propValue: any = {};
|
||||||
|
this.actionProps.forEach((value) => {
|
||||||
|
propValue[value.props.name] = value.props.modelValue
|
||||||
|
});
|
||||||
|
return propValue;
|
||||||
|
};
|
||||||
|
prevNodeId?: string;
|
||||||
|
nextNodeIds: Map<string, string>;
|
||||||
|
constructor(
|
||||||
|
name: string,
|
||||||
|
title: string,
|
||||||
|
inputPoint: string,
|
||||||
|
outputPoint: Array<string> = [],
|
||||||
|
actionProps: Array<IActionProperty> = [ActionProperty.defaultProperty()],
|
||||||
|
) {
|
||||||
|
this.id = uuidv4();
|
||||||
|
this.name = name;
|
||||||
|
this.inputPoint = inputPoint;
|
||||||
|
this.outputPoints = outputPoint;
|
||||||
|
const defProp = ActionProperty.defaultProperty();
|
||||||
|
defProp.props.modelValue = title;
|
||||||
|
this.actionProps = actionProps;
|
||||||
|
const prop = this.actionProps.find((prop) => prop.props.name === defProp.props.name);
|
||||||
|
if (prop === undefined) {
|
||||||
|
this.actionProps.unshift(defProp);
|
||||||
|
}
|
||||||
|
this.nextNodeIds = new Map<string, string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* ルートアクション定義
|
||||||
|
*/
|
||||||
|
export class RootAction implements IActionNode {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
subTitle: string;
|
||||||
|
inputPoint: string;
|
||||||
|
varName: IProp | undefined=undefined;
|
||||||
|
//出力ポイント(条件分岐以外未使用)
|
||||||
|
outputPoints: Array<string>;
|
||||||
|
isRoot: boolean;
|
||||||
|
actionProps: Array<IActionProperty>;
|
||||||
|
ActionValue: object;
|
||||||
|
prevNodeId?: string = undefined;
|
||||||
|
nextNodeIds: Map<string, string>;
|
||||||
|
constructor(
|
||||||
|
name: string,
|
||||||
|
title: string,
|
||||||
|
subTitle: string,
|
||||||
|
) {
|
||||||
|
this.id = uuidv4();
|
||||||
|
this.name = name;
|
||||||
|
this.title = title;
|
||||||
|
this.subTitle = subTitle;
|
||||||
|
this.inputPoint = '';
|
||||||
|
this.outputPoints = [];
|
||||||
|
this.isRoot = true;
|
||||||
|
this.actionProps = [];
|
||||||
|
this.ActionValue = {};
|
||||||
|
this.nextNodeIds = new Map<string, string>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* アクションフローの定義
|
||||||
|
*/
|
||||||
|
export class ActionFlow implements IActionFlow {
|
||||||
|
id: string;
|
||||||
|
actionNodes: Array<IActionNode>;
|
||||||
|
constructor(actionNodes: Array<IActionNode> | RootAction) {
|
||||||
|
if (actionNodes instanceof Array) {
|
||||||
|
this.actionNodes = actionNodes;
|
||||||
|
} else {
|
||||||
|
this.actionNodes = [actionNodes];
|
||||||
|
}
|
||||||
|
this.id = '';
|
||||||
|
//this.id = uuidv4();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* ノードを追加する
|
||||||
|
* 1.ID採番する
|
||||||
|
* 2.前のノードを関連付け
|
||||||
|
* @param newNode 新規追加するノード
|
||||||
|
* @param prevNode 前のノード
|
||||||
|
* @param inputPoint 入力ポイント
|
||||||
|
* @returns 追加されたノード
|
||||||
|
*/
|
||||||
|
addNode(
|
||||||
|
newNode: IActionNode,
|
||||||
|
prevNode?: IActionNode,
|
||||||
|
inputPoint?: string): IActionNode {
|
||||||
|
if (inputPoint !== undefined) {
|
||||||
|
newNode.inputPoint = inputPoint;
|
||||||
|
}
|
||||||
|
if (prevNode !== undefined) {
|
||||||
|
this.resetNodeRelation(prevNode, newNode, inputPoint || '');
|
||||||
|
} else {
|
||||||
|
prevNode = this.actionNodes[this.actionNodes.length - 1];
|
||||||
|
this.connectNodes(prevNode, newNode, inputPoint || '');
|
||||||
|
}
|
||||||
|
const index = this.actionNodes.findIndex(node => node.id === prevNode?.id)
|
||||||
|
if (index >= 0) {
|
||||||
|
this.actionNodes.splice(index + 1, 0, newNode);
|
||||||
|
} else {
|
||||||
|
this.actionNodes.push(newNode);
|
||||||
|
}
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* ノードを削除する
|
||||||
|
* @param delNode
|
||||||
|
*/
|
||||||
|
removeNode(targetNode: IActionNode): boolean {
|
||||||
|
if (!targetNode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (targetNode.isRoot) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this.disconnectFromPrevNode(targetNode);
|
||||||
|
this.reconnectOrRemoveNextNodes(targetNode);
|
||||||
|
this.removeFromActionNodes(targetNode.id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/***
|
||||||
|
* 目標ノードの次のノードを全部削除する
|
||||||
|
*/
|
||||||
|
removeAllNext(targetNodeId: string) {
|
||||||
|
if (!targetNodeId || targetNodeId === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const targetNode = this.findNodeById(targetNodeId);
|
||||||
|
if (!targetNode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (targetNode.nextNodeIds.size == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const [, id] of targetNode.nextNodeIds) {
|
||||||
|
this.removeAll(id);
|
||||||
|
}
|
||||||
|
targetNode.nextNodeIds.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* 目標ノードの次のノードを全部削除する
|
||||||
|
*/
|
||||||
|
removeAll(targetNodeId: string) {
|
||||||
|
if (!targetNodeId || targetNodeId === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const targetNode = this.findNodeById(targetNodeId);
|
||||||
|
if (!targetNode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (targetNode.nextNodeIds.size == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const [, id] of targetNode.nextNodeIds) {
|
||||||
|
this.removeAll(id);
|
||||||
|
}
|
||||||
|
this.removeNode(targetNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 断开与前一个节点的连接
|
||||||
|
disconnectFromPrevNode(targetNode: IActionNode): void {
|
||||||
|
const prevNodeId = targetNode.prevNodeId;
|
||||||
|
if (prevNodeId) {
|
||||||
|
const prevNode = this.findNodeById(prevNodeId);
|
||||||
|
if (prevNode) {
|
||||||
|
for (const [key, value] of prevNode.nextNodeIds) {
|
||||||
|
if (value === targetNode.id) {
|
||||||
|
prevNode.nextNodeIds.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// actionNodes からノードを削除する
|
||||||
|
private removeFromActionNodes(targetNodeId: string): void {
|
||||||
|
const index = this.actionNodes.findIndex(node => node.id === targetNodeId);
|
||||||
|
if (index > -1) {
|
||||||
|
this.actionNodes.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ノード削除時、前のノードと次のノードを接続する
|
||||||
|
* @param targetNode
|
||||||
|
*/
|
||||||
|
reconnectOrRemoveNextNodes(targetNode: IActionNode): void {
|
||||||
|
if (!targetNode || !targetNode.prevNodeId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//前のノードを取得
|
||||||
|
const prevNode = this.findNodeById(targetNode.prevNodeId);
|
||||||
|
if (!prevNode) return;
|
||||||
|
//次のノード取得
|
||||||
|
const nextNodeIds = targetNode.nextNodeIds;
|
||||||
|
if (nextNodeIds.size == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//次のノード一つの場合
|
||||||
|
if (nextNodeIds.size == 1) {
|
||||||
|
const nextNodeId = nextNodeIds.get('');
|
||||||
|
if (!nextNodeId) return;
|
||||||
|
const nextNode = this.findNodeById(nextNodeId);
|
||||||
|
if (!nextNode) return;
|
||||||
|
this.connectNodes(prevNode,nextNode,targetNode.inputPoint || '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//二つ以上の場合
|
||||||
|
for (const [point, nextid] of nextNodeIds) {
|
||||||
|
const nextNode = this.findNodeById(nextid);
|
||||||
|
if (!nextNode) return;
|
||||||
|
if (!this.connectNodes(prevNode, nextNode, point)) {
|
||||||
|
this.removeAllNext(nextid);
|
||||||
|
this.removeFromActionNodes(nextid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 二つノードを接続する
|
||||||
|
* @param prevNode
|
||||||
|
* @param nextNodeId
|
||||||
|
* @param point
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
connectNodes(prevNode: IActionNode, nextNode: IActionNode, point: string): boolean {
|
||||||
|
if (!prevNode || !nextNode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!nextNode) return false;
|
||||||
|
prevNode.nextNodeIds.set(point, nextNode.id);
|
||||||
|
nextNode.prevNodeId = prevNode.id;
|
||||||
|
nextNode.inputPoint = point;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param prevNode ノード挿入時の接続をリセットする
|
||||||
|
* @param newNode
|
||||||
|
* @param inputPoint
|
||||||
|
*/
|
||||||
|
resetNodeRelation(prevNode: IActionNode, newNode: IActionNode, inputPoint?: string) {
|
||||||
|
//元の次のノードを取得
|
||||||
|
const originalNextNodeId = prevNode.nextNodeIds.get(inputPoint || '');
|
||||||
|
prevNode.nextNodeIds.set(inputPoint || '', newNode.id);
|
||||||
|
newNode.prevNodeId = prevNode.id;
|
||||||
|
newNode.inputPoint=inputPoint||'';
|
||||||
|
this.setNewNodeNextId(newNode, originalNextNodeId, inputPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 後ノードと新ノードの関連付け
|
||||||
|
* @param newNode
|
||||||
|
* @param originalNextNodeId
|
||||||
|
* @param inputPoint
|
||||||
|
*/
|
||||||
|
private setNewNodeNextId(newNode: IActionNode, originalNextNodeId: string | undefined, inputPoint?: string) {
|
||||||
|
// 元の接続ノードが存在する
|
||||||
|
if (!originalNextNodeId) {return;}
|
||||||
|
const originNextNode = this.findNodeById(originalNextNodeId);
|
||||||
|
if (!originNextNode) {return;}
|
||||||
|
// 新しいノードの outputPoints に該当 inputPointが存在するか場合をチェックする
|
||||||
|
if (newNode.outputPoints.includes(inputPoint || '')) {
|
||||||
|
newNode.nextNodeIds.set(inputPoint || '', originalNextNodeId);
|
||||||
|
originNextNode.prevNodeId=newNode.id;
|
||||||
|
} else {
|
||||||
|
// inputPointが存在しない場合、outputPointのポイントの任意ポートを選択する
|
||||||
|
const alternativeOutputPoint = newNode.outputPoints.length > 0 ? newNode.outputPoints[0] : '';
|
||||||
|
newNode.nextNodeIds.set(alternativeOutputPoint, originalNextNodeId);
|
||||||
|
originNextNode.prevNodeId=newNode.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* IDでActionNodeを取得する
|
||||||
|
*/
|
||||||
|
findNodeById(id: string): IActionNode | undefined {
|
||||||
|
return this.actionNodes.find((node) => node.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
|
getVarNames(currentNode:IActionNode):IActionVariable[]{
|
||||||
|
let varNames:IActionVariable[]=[];
|
||||||
|
if(currentNode.prevNodeId!==undefined){
|
||||||
|
const prevNode=this.findNodeById(currentNode.prevNodeId);
|
||||||
|
if(prevNode!==undefined){
|
||||||
|
varNames = this.getPrevVarNames(prevNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return varNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrevVarNames(prevNode:IActionNode):IActionVariable[]{
|
||||||
|
let varNames:IActionVariable[]=[];
|
||||||
|
if(prevNode.varName!==undefined){
|
||||||
|
varNames.unshift({
|
||||||
|
actionName:prevNode.name,
|
||||||
|
displayName:prevNode.varName.displayName,
|
||||||
|
name:prevNode.varName.modelValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if(prevNode.prevNodeId!==undefined){
|
||||||
|
const prevPrevNode=this.findNodeById(prevNode.prevNodeId);
|
||||||
|
if(prevPrevNode!==undefined){
|
||||||
|
const prevVars = this.getPrevVarNames(prevPrevNode);
|
||||||
|
varNames=[...prevVars,...varNames];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return varNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
actionNodes: this.actionNodes.map(node => {
|
||||||
|
const { nextNodeIds, ...rest } = node;
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
nextNodeIds: Object.fromEntries(nextNodeIds)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getColumns(node:IActionNode):number{
|
||||||
|
let result= 1;
|
||||||
|
if(node.outputPoints && node.outputPoints.length>1){
|
||||||
|
result += node.outputPoints.length -1;
|
||||||
|
}
|
||||||
|
let nextNode;
|
||||||
|
for (const [key, id] of node.nextNodeIds) {
|
||||||
|
nextNode=this.findNodeById(id);
|
||||||
|
if(nextNode){
|
||||||
|
result +=this.getColumns(nextNode)-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRoot(): IActionNode | undefined {
|
||||||
|
return this.actionNodes.find(node => node.isRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
createNewId():string{
|
||||||
|
this.id=uuidv4();
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: string): ActionFlow {
|
||||||
|
const parsedObject = JSON.parse(json);
|
||||||
|
|
||||||
|
const actionNodes = parsedObject.actionNodes.map((node: any) => {
|
||||||
|
const nodeClass = !node.isRoot ? new ActionNode(node.name, node.title, node.inputPoint, node.outputPoints, node.actionProps)
|
||||||
|
: new RootAction(node.name, node.title, node.subTitle);
|
||||||
|
nodeClass.nextNodeIds = new Map<string,string>(Object.entries(node.nextNodeIds));
|
||||||
|
nodeClass.prevNodeId = node.prevNodeId;
|
||||||
|
nodeClass.id = node.id;
|
||||||
|
return nodeClass;
|
||||||
|
});
|
||||||
|
const actionFlow = new ActionFlow(actionNodes);
|
||||||
|
actionFlow.id = parsedObject.id;
|
||||||
|
return actionFlow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
336
frontend/src/types/Conditions.ts
Normal file
336
frontend/src/types/Conditions.ts
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
//ノード種別
|
||||||
|
export enum NodeType{
|
||||||
|
Root = 'root',
|
||||||
|
LogicGroup ='logicgroup',
|
||||||
|
Condition ='condition'
|
||||||
|
}
|
||||||
|
|
||||||
|
//ロジックオペレーター
|
||||||
|
export enum LogicalOperator{
|
||||||
|
AND = 'AND',
|
||||||
|
OR = 'OR'
|
||||||
|
}
|
||||||
|
|
||||||
|
//条件オペレーター
|
||||||
|
export enum Operator{
|
||||||
|
Equal = '=',
|
||||||
|
NotEqual='!=',
|
||||||
|
Greater = '>',
|
||||||
|
GreaterOrEqual = '>=',
|
||||||
|
Less = '<',
|
||||||
|
LessOrEqual = '<=',
|
||||||
|
Contains = 'contains',
|
||||||
|
NotContains = 'not contains',
|
||||||
|
StartWith = 'start With',
|
||||||
|
EndWith = 'end with',
|
||||||
|
NotStartWith = 'not start with',
|
||||||
|
NotEndWith = 'not end with'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// INode
|
||||||
|
export interface INode {
|
||||||
|
index:number;
|
||||||
|
type: NodeType;
|
||||||
|
header:string;
|
||||||
|
parent: INode | null;
|
||||||
|
logicalOperator:LogicalOperator
|
||||||
|
}
|
||||||
|
|
||||||
|
// ロジックノード
|
||||||
|
export class GroupNode implements INode {
|
||||||
|
index:number;
|
||||||
|
type: NodeType;
|
||||||
|
children: INode[];
|
||||||
|
parent: INode | null;
|
||||||
|
logicalOperator: LogicalOperator;
|
||||||
|
get label():string{
|
||||||
|
return this.logicalOperator;
|
||||||
|
}
|
||||||
|
get header():string{
|
||||||
|
return this.type===NodeType.Root?'root':'generic';
|
||||||
|
}
|
||||||
|
get expanded():boolean{
|
||||||
|
return this.children.length>0;
|
||||||
|
}
|
||||||
|
constructor(logicOp:LogicalOperator, parent: INode | null) {
|
||||||
|
this.index=0;
|
||||||
|
this.type = parent==null?NodeType.Root: NodeType.LogicGroup;
|
||||||
|
this.logicalOperator = logicOp;
|
||||||
|
this.parent=parent;
|
||||||
|
this.children=[];
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: any, parent: INode | null = null): GroupNode {
|
||||||
|
const node = new GroupNode(json.logicalOperator, parent);
|
||||||
|
node.index=json.index;
|
||||||
|
node.children = json.children.map((childJson: any) => {
|
||||||
|
return childJson.type === NodeType.LogicGroup
|
||||||
|
? GroupNode.fromJSON(childJson, node)
|
||||||
|
: ConditionNode.fromJSON(childJson, node);
|
||||||
|
});
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 条件式ノード
|
||||||
|
export class ConditionNode implements INode {
|
||||||
|
index: number;
|
||||||
|
type: NodeType;
|
||||||
|
parent:INode;
|
||||||
|
get logicalOperator(): LogicalOperator{
|
||||||
|
return this.parent.logicalOperator;
|
||||||
|
};
|
||||||
|
object: any; // 比較元
|
||||||
|
operator: Operator; // 比較子
|
||||||
|
value: any;
|
||||||
|
get header():string{
|
||||||
|
return 'generic';
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(object: any, operator: Operator, value: any, parent: GroupNode) {
|
||||||
|
this.index=0;
|
||||||
|
this.type = NodeType.Condition;
|
||||||
|
this.object = object;
|
||||||
|
this.operator = operator;
|
||||||
|
this.value = value;
|
||||||
|
this.parent=parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: any, parent: GroupNode): ConditionNode {
|
||||||
|
const node= new ConditionNode(
|
||||||
|
json.object,
|
||||||
|
json.operator,
|
||||||
|
json.value,
|
||||||
|
parent
|
||||||
|
);
|
||||||
|
node.index=json.index;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 条件式の管理クラス
|
||||||
|
export class ConditionTree {
|
||||||
|
root: GroupNode;
|
||||||
|
maxIndex:number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.maxIndex=0;
|
||||||
|
this.root = new GroupNode(LogicalOperator.AND, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ノード追加
|
||||||
|
addNode(parent: GroupNode, node: INode): void {
|
||||||
|
this.maxIndex++;
|
||||||
|
node.index=this.maxIndex;
|
||||||
|
parent.children.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ノード削除
|
||||||
|
removeNode(node: INode): void {
|
||||||
|
if (node.parent === null) {
|
||||||
|
throw new Error('ルートノード削除できません');
|
||||||
|
} else {
|
||||||
|
const parent = node.parent as GroupNode;
|
||||||
|
const index = parent.children.indexOf(node);
|
||||||
|
if (index > -1) {
|
||||||
|
parent.children.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findByIndex(index:number):INode|undefined{
|
||||||
|
return this.findChildren(this.root,index);
|
||||||
|
}
|
||||||
|
|
||||||
|
findChildren(parent: GroupNode, index: number): INode | undefined {
|
||||||
|
if (parent.index === index) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
for (const node of parent.children) {
|
||||||
|
if (node.index === index) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
if (node.type !== NodeType.Condition) {
|
||||||
|
const foundNode = this.findChildren(node as GroupNode, index);
|
||||||
|
if (foundNode) {
|
||||||
|
return foundNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getMaxIndex(node:INode):number{
|
||||||
|
let maxIndex:number=node.index;
|
||||||
|
if(node.type!==NodeType.Condition){
|
||||||
|
const groupNode = node as GroupNode;
|
||||||
|
groupNode.children.forEach((child)=>{
|
||||||
|
const childMax = this.getMaxIndex(child);
|
||||||
|
if(childMax>maxIndex){
|
||||||
|
maxIndex=childMax;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return maxIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
//条件式を表示する
|
||||||
|
buildConditionString(node:INode){
|
||||||
|
if (node.type !== NodeType.Condition) {
|
||||||
|
let conditionString = '(';
|
||||||
|
const groupNode = node as GroupNode;
|
||||||
|
for (let i = 0; i < groupNode.children.length; i++) {
|
||||||
|
const childConditionString = this.buildConditionString(groupNode.children[i]);
|
||||||
|
if (childConditionString !== '') {
|
||||||
|
conditionString += childConditionString;
|
||||||
|
if (i < groupNode.children.length - 1) {
|
||||||
|
conditionString += ` ${groupNode.logicalOperator} `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conditionString += ')';
|
||||||
|
return conditionString;
|
||||||
|
} else {
|
||||||
|
const condNode=node as ConditionNode;
|
||||||
|
if (condNode.object && condNode.operator ) {
|
||||||
|
let value=condNode.value;
|
||||||
|
if(value && typeof value ==='object' && ('label' in value)){
|
||||||
|
value =condNode.value.label;
|
||||||
|
}
|
||||||
|
return `${condNode.object.name} ${condNode.operator} '${value}'`;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param node ノード移動
|
||||||
|
* @param direction
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
moveNode(node:INode, direction: 'up' | 'down'): void {
|
||||||
|
if (!node || !node.parent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = node.parent as GroupNode;
|
||||||
|
const currentIndex = parent.children.findIndex(child => child === node);
|
||||||
|
if (currentIndex === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
|
||||||
|
|
||||||
|
// 範囲外のインデックスの処理
|
||||||
|
if (newIndex >= 0 && newIndex < parent.children.length) {
|
||||||
|
// ノードの位置を入れ替える
|
||||||
|
[parent.children[currentIndex], parent.children[newIndex]] = [parent.children[newIndex], parent.children[currentIndex]];
|
||||||
|
return; // 範囲外なら移動しない
|
||||||
|
}else if(newIndex<0 && parent.parent){
|
||||||
|
this.removeNode(node);
|
||||||
|
const parentIndex = (parent.parent as GroupNode).children.findIndex(child => child === parent);
|
||||||
|
(parent.parent as GroupNode).children.splice(parentIndex, 0, node);
|
||||||
|
node.parent = parent.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getGroups(parent:GroupNode):number[]{
|
||||||
|
const groups:number[]=[];
|
||||||
|
groups.push(parent.index);
|
||||||
|
parent.children.forEach((node)=>{
|
||||||
|
if(node.type!==NodeType.Condition){
|
||||||
|
groups.push(...this.getGroups(node as GroupNode));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件ノードをグループ化
|
||||||
|
* @param nodes 結合ノードを選択する
|
||||||
|
* @param logicOp
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
createGroupNode(firstNode:INode,nodes: INode[], logicOp: LogicalOperator): GroupNode | null {
|
||||||
|
if (nodes.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最初のノードの親ノードを取得
|
||||||
|
const parent = firstNode.parent as GroupNode;
|
||||||
|
if (!parent) {
|
||||||
|
throw new Error('ルートノードをグループ化できません');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 親ノードを取得
|
||||||
|
const filteredNodes = nodes.filter(node => node.parent === parent);
|
||||||
|
|
||||||
|
// 新しいグループノードを作成
|
||||||
|
const newGroup = new GroupNode(logicOp, parent);
|
||||||
|
this.maxIndex++;
|
||||||
|
newGroup.index = this.maxIndex;
|
||||||
|
|
||||||
|
// 新しいグループノードの挿入位置を取得
|
||||||
|
let firstNodeIndex = parent.children.length;
|
||||||
|
if (filteredNodes.length > 0) {
|
||||||
|
firstNodeIndex = parent.children.indexOf(filteredNodes[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredNodes.forEach(node => {
|
||||||
|
// 元の親ノードから削除する
|
||||||
|
const nodeIndex = parent.children.indexOf(node);
|
||||||
|
parent.children.splice(nodeIndex, 1);
|
||||||
|
|
||||||
|
// 新しいグループに追加
|
||||||
|
node.parent = newGroup;
|
||||||
|
newGroup.children.push(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 新しいGroupNodeを挿入する
|
||||||
|
parent.children.splice(firstNodeIndex, 0, newGroup);
|
||||||
|
return newGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GroupNodeを解散する
|
||||||
|
* @param groupNode 対象グループノード
|
||||||
|
*/
|
||||||
|
dissolveGroupNode(groupNode: GroupNode): void {
|
||||||
|
if (groupNode.parent === null || groupNode.type !== NodeType.LogicGroup) {
|
||||||
|
throw new Error('ルートノードと非グループノードを解散することができません');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 親ノードを取得
|
||||||
|
const parent = groupNode.parent as GroupNode;
|
||||||
|
const groupIndex = parent.children.indexOf(groupNode);
|
||||||
|
|
||||||
|
// 子ノードをリセットする
|
||||||
|
groupNode.children.forEach(child => {
|
||||||
|
child.parent = parent;
|
||||||
|
parent.children.splice(groupIndex, 0, child);
|
||||||
|
});
|
||||||
|
|
||||||
|
//グループノードを削除する
|
||||||
|
parent.children.splice(groupIndex + groupNode.children.length, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jsonから復元
|
||||||
|
fromJson(jsonString: string): INode {
|
||||||
|
const json = JSON.parse(jsonString);
|
||||||
|
this.root = GroupNode.fromJSON(json) as GroupNode;
|
||||||
|
this.maxIndex=this.getMaxIndex(this.root);
|
||||||
|
return this.root;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJson():string{
|
||||||
|
return JSON.stringify(this.root, (key, value) => {
|
||||||
|
if (key === 'parent') {
|
||||||
|
return value ? value.type : null;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
197
frontend/src/types/KintoneEvents.ts
Normal file
197
frontend/src/types/KintoneEvents.ts
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import {IActionFlow} from './ActionTypes';
|
||||||
|
export interface IKintoneEventNode {
|
||||||
|
label: string;
|
||||||
|
header:string;
|
||||||
|
eventId:string;
|
||||||
|
parentId:string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IKintoneEvent extends IKintoneEventNode {
|
||||||
|
hasFlow: boolean;
|
||||||
|
flowData?: IActionFlow;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IKintoneEventGroup extends IKintoneEventNode {
|
||||||
|
events: IKintoneEventNode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class kintoneEvent implements IKintoneEvent{
|
||||||
|
eventId: string;
|
||||||
|
parentId:string;
|
||||||
|
get hasFlow(): boolean{
|
||||||
|
return this.flowData!==undefined && this.flowData.actionNodes.length>1
|
||||||
|
};
|
||||||
|
flowData?: IActionFlow | undefined;
|
||||||
|
label: string;
|
||||||
|
get header():string{
|
||||||
|
return "EVENT";
|
||||||
|
}
|
||||||
|
constructor(label:string,eventId:string,parentId:string){
|
||||||
|
this.eventId=eventId;
|
||||||
|
this.label=label;
|
||||||
|
this.parentId=parentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class kintoneEventGroup implements IKintoneEventGroup{
|
||||||
|
eventId: string;
|
||||||
|
parentId:string;
|
||||||
|
label: string;
|
||||||
|
events: IKintoneEventNode[];
|
||||||
|
get header():string{
|
||||||
|
return "EVENTGROUP";
|
||||||
|
}
|
||||||
|
constructor(eventId:string,label:string,events:IKintoneEventNode[],parentId:string){
|
||||||
|
this.eventId=eventId;
|
||||||
|
this.label=label;
|
||||||
|
this.events=events;
|
||||||
|
this.parentId=parentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class kintoneEventForChange implements IKintoneEventGroup{
|
||||||
|
eventId: string;
|
||||||
|
parentId:string;
|
||||||
|
label: string;
|
||||||
|
events: IKintoneEventNode[];
|
||||||
|
get header():string{
|
||||||
|
return "CHANGE";
|
||||||
|
}
|
||||||
|
constructor(eventId:string,label:string,events:IKintoneEventNode[],parentId:string){
|
||||||
|
this.eventId=eventId;
|
||||||
|
this.label=label;
|
||||||
|
this.events=events;
|
||||||
|
this.parentId=parentId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export class KintoneEventManager {
|
||||||
|
public screens: IKintoneEventGroup[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.screens = this.getKintoneEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bindFlows(flows:IActionFlow[]){
|
||||||
|
this.screens=this.getKintoneEvents();
|
||||||
|
for (const flow of flows){
|
||||||
|
const eventId =flow.getRoot()?.name;
|
||||||
|
if(eventId!==undefined){
|
||||||
|
const eventNode = this.findEventById(eventId);
|
||||||
|
if(eventNode!==null && eventNode.header==="EVENT"){
|
||||||
|
const event =eventNode as kintoneEvent;
|
||||||
|
event.flowData=flow;
|
||||||
|
}else{
|
||||||
|
//EventGroupのIDを取得
|
||||||
|
const lastIndex = eventId.lastIndexOf(".");
|
||||||
|
const groupId=eventId.substring(0,lastIndex);
|
||||||
|
const eventNode = this.findEventById(groupId);
|
||||||
|
if(eventNode && (eventNode.header==="EVENTGROUP" || eventNode.header==="CHANGE")){
|
||||||
|
const groupEvent=eventNode as kintoneEventGroup;
|
||||||
|
const newEvent =new kintoneEvent(
|
||||||
|
flow.getRoot()?.subTitle || "",
|
||||||
|
eventId,
|
||||||
|
groupEvent.parentId
|
||||||
|
);
|
||||||
|
newEvent.flowData=flow;
|
||||||
|
groupEvent.events.push(newEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public findEventById(eventId: string): IKintoneEventNode | null {
|
||||||
|
const screen=this.findScreen(eventId);
|
||||||
|
if(screen) {return screen;}
|
||||||
|
for (const screen of this.screens) {
|
||||||
|
for (const event of screen.events) {
|
||||||
|
if (event.eventId === eventId) {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
if(event.header==="EVENTGROUP"||event.header==="CHANGE"){
|
||||||
|
const eventGroup = event as IKintoneEventGroup;
|
||||||
|
const targetEvent = eventGroup.events.find((ev)=>{
|
||||||
|
return ev.eventId===eventId;
|
||||||
|
})
|
||||||
|
if(targetEvent){
|
||||||
|
return targetEvent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public findScreen(eventId:string):IKintoneEventGroup|undefined{
|
||||||
|
return this.screens.find(screen=>screen.eventId==eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getKintoneEvents():IKintoneEventGroup[]{
|
||||||
|
return [
|
||||||
|
new kintoneEventGroup("app.record.create","レコード追加画面",[
|
||||||
|
new kintoneEvent('レコード追加画面を表示した後','app.record.create.show',"app.record.create"),
|
||||||
|
new kintoneEvent('保存をクリックしたとき','app.record.create.submit',"app.record.create"),
|
||||||
|
new kintoneEvent('保存が成功したとき','app.record.create.submit.success',"app.record.create"),
|
||||||
|
new kintoneEventForChange('app.record.create.change','フィールドの値を変更したとき',[],"app.record.create"),
|
||||||
|
new kintoneEventGroup('app.record.create.show.customButtonClick','ボタンをクリックした時',[],"app.record.create")
|
||||||
|
],""),
|
||||||
|
new kintoneEventGroup("app.record.detail","レコード詳細画面",[
|
||||||
|
new kintoneEvent('レコード詳細画面を表示した後','app.record.detail.show',"app.record.detail"),
|
||||||
|
new kintoneEvent('レコードを削除するとき','app.record.detail.delete.submit',"app.record.detail"),
|
||||||
|
new kintoneEvent('プロセス管理のアクションを実行したとき','app.record.detail.process.proceed',"app.record.detail"),
|
||||||
|
new kintoneEventGroup('app.record.detail.show.customButtonClick','ボタンをクリックした時',[],"app.record.detail"),
|
||||||
|
],""),
|
||||||
|
new kintoneEventGroup("app.record.edit","レコード編集画面",[
|
||||||
|
new kintoneEvent('レコード編集画面を表示した後','app.record.edit.show',"app.record.edit"),
|
||||||
|
new kintoneEvent('保存をクリックしたとき','app.record.edit.submit',"app.record.edit"),
|
||||||
|
new kintoneEvent('保存が成功したとき','app.record.edit.submit.success',"app.record.edit"),
|
||||||
|
new kintoneEventForChange('app.record.edit.change','フィールドの値を変更したとき',[],"app.record.edit"),
|
||||||
|
new kintoneEventGroup('app.record.edit.show.customButtonClick','ボタンをクリックした時',[],"app.record.edit"),
|
||||||
|
],""),
|
||||||
|
new kintoneEventGroup("app.record.index","レコード一覧画面",[
|
||||||
|
new kintoneEvent('一覧画面を表示した後', 'app.record.index.show',"app.record.index"),
|
||||||
|
new kintoneEvent('インライン編集を開始したとき','app.record.index.edit.show',"app.record.index"),
|
||||||
|
new kintoneEvent('インライン編集の【保存】をクリックしたとき','app.record.index.edit.submit',"app.record.index"),
|
||||||
|
new kintoneEvent('インライン編集の保存が成功したとき', 'app.record.index.edit.submit.success',"app.record.index"),
|
||||||
|
new kintoneEventForChange('app.record.index.edit.change','インライン編集のフィールド値を変更したとき' ,[],"app.record.index"),
|
||||||
|
new kintoneEventGroup('app.record.detail.show.customButtonClick','ボタンをクリックした時',[],"app.record.index"),
|
||||||
|
],"")
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// export const kintoneEvents:KintoneEventManager = new KintoneEventManager([
|
||||||
|
// new kintoneEventGroup("app.record.create","レコード追加画面",[
|
||||||
|
// new kintoneEvent('レコード追加画面を表示した後','app.record.create.show',"app.record.create"),
|
||||||
|
// new kintoneEvent('保存をクリックしたとき','app.record.create.submit',"app.record.create"),
|
||||||
|
// new kintoneEvent('保存が成功したとき','app.record.create.submit.success',"app.record.create"),
|
||||||
|
// new kintoneEventForChange('app.record.create.change','フィールドの値を変更したとき',[],"app.record.create"),
|
||||||
|
// new kintoneEventGroup('app.record.create.customButtonClick','ボタンをクリックした時',[],"app.record.create")
|
||||||
|
// ],""),
|
||||||
|
// new kintoneEventGroup("app.record.detail","レコード詳細画面",[
|
||||||
|
// new kintoneEvent('レコード詳細画面を表示した後','app.record.detail.show',"app.record.detail"),
|
||||||
|
// new kintoneEvent('レコードを削除するとき','app.record.detail.delete.submit',"app.record.detail"),
|
||||||
|
// new kintoneEvent('プロセス管理のアクションを実行したとき','app.record.detail.process.proceed',"app.record.detail"),
|
||||||
|
// new kintoneEventGroup('app.record.detail.customButtonClick','ボタンをクリックした時',[],"app.record.detail"),
|
||||||
|
// ],""),
|
||||||
|
// new kintoneEventGroup("app.record.edit","レコード編集画面",[
|
||||||
|
// new kintoneEvent('レコード編集画面を表示した後','app.record.edit.show',"app.record.edit"),
|
||||||
|
// new kintoneEvent('保存をクリックしたとき','app.record.edit.submit',"app.record.edit"),
|
||||||
|
// new kintoneEvent('保存が成功したとき','app.record.edit.submit.success',"app.record.edit"),
|
||||||
|
// new kintoneEventForChange('app.record.edit.change','フィールドの値を変更したとき',[],"app.record.edit"),
|
||||||
|
// new kintoneEventGroup('app.record.edit.customButtonClick','ボタンをクリックした時',[],"app.record.edit"),
|
||||||
|
// ],""),
|
||||||
|
// new kintoneEventGroup("app.record.index","レコード一覧画面",[
|
||||||
|
// new kintoneEvent('一覧画面を表示した後', 'app.record.index.show',"app.record.index"),
|
||||||
|
// new kintoneEvent('インライン編集を開始したとき','app.record.index.edit.show',"app.record.index"),
|
||||||
|
// new kintoneEvent('インライン編集の【保存】をクリックしたとき','app.record.index.edit.submit',"app.record.index"),
|
||||||
|
// new kintoneEvent('インライン編集の保存が成功したとき', 'app.record.index.edit.submit.success',"app.record.index"),
|
||||||
|
// new kintoneEventForChange('app.record.index.edit.change','インライン編集のフィールド値を変更したとき' ,[],"app.record.index"),
|
||||||
|
// new kintoneEventGroup('app.record.detail.customButtonClick','ボタンをクリックした時',[],"app.record.index"),
|
||||||
|
// ],"")
|
||||||
|
// ]);
|
||||||
1075
frontend/vue3.0概要.md
Normal file
1075
frontend/vue3.0概要.md
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user