Compare commits
22 Commits
feature-xj
...
plugin-inf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3540becf6f | ||
|
|
63099eda8b | ||
|
|
65d89b0462 | ||
|
|
c6ded099fa | ||
|
|
c072233593 | ||
|
|
4e296c1555 | ||
| 016fcaab29 | |||
|
|
bebc1ec9fa | ||
|
|
f71c3d2123 | ||
|
|
d79ce8d06b | ||
|
|
fc9c3a5e81 | ||
|
|
6df72a1ae3 | ||
|
|
372dbe50f7 | ||
|
|
68fde6d490 | ||
|
|
c398dee21e | ||
|
|
f2ab310b6d | ||
|
|
ca0f24465b | ||
|
|
3cc4b65460 | ||
|
|
a6cf95b76d | ||
|
|
484ab9fdae | ||
|
|
78bba2502f | ||
|
|
c78b3cb5c0 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,7 +1,3 @@
|
||||
.vscode
|
||||
.mypy_cache
|
||||
docker-stack.yml
|
||||
backend/pyvenv.cfg
|
||||
backend/Include/
|
||||
backend/Scripts/
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -25,15 +25,11 @@ async def login(
|
||||
minutes=security.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
if user.is_superuser:
|
||||
roles = "super"
|
||||
permissions = "ALL"
|
||||
permissions = "admin"
|
||||
else:
|
||||
roles = ";".join(role.name for role in user.roles)
|
||||
perlst = [perm.privilege for role in user.roles for perm in role.permissions]
|
||||
permissions =";".join(list(set(perlst)))
|
||||
|
||||
permissions = "user"
|
||||
access_token = security.create_access_token(
|
||||
data={"sub": user.id, "roles":roles,"permissions": permissions ,},
|
||||
data={"sub": user.id, "permissions": permissions},
|
||||
expires_delta=access_token_expires,
|
||||
)
|
||||
|
||||
|
||||
@@ -156,10 +156,10 @@ def getsettingfromexcel(df):
|
||||
des = df.iloc[2,2]
|
||||
return {"name":appname,"description":des}
|
||||
|
||||
def getsettingfromkintone(app:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
def getsettingfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{env.BASE_URL}{config.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)
|
||||
return r.json()
|
||||
|
||||
@@ -171,100 +171,59 @@ def analysesettings(excel,kintone):
|
||||
updatesettings[key] = excel[key]
|
||||
return updatesettings
|
||||
|
||||
def createkintoneapp(name:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
def createkintoneapp(name:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
data = {"name":name}
|
||||
url = f"{env.BASE_URL}{config.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))
|
||||
return r.json()
|
||||
|
||||
def updateappsettingstokintone(app:str,updates:dict,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/settings.json"
|
||||
def updateappsettingstokintone(app:str,updates:dict,c:config.KINTONE_ENV):
|
||||
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/settings.json"
|
||||
data = {"app":app}
|
||||
data.update(updates)
|
||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def addfieldstokintone(app:str,fields:dict,env:config.KINTONE_ENV,revision:str = None):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
def addfieldstokintone(app:str,fields:dict,c:config.KINTONE_ENV,revision:str = None):
|
||||
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/form/fields.json"
|
||||
if revision != None:
|
||||
data = {"app":app,"revision":revision,"properties":fields}
|
||||
else:
|
||||
data = {"app":app,"properties":fields}
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def updatefieldstokintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
|
||||
def updatefieldstokintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
||||
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/form/fields.json"
|
||||
data = {"app":app,"properties":fields}
|
||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||
return r.json()
|
||||
|
||||
def deletefieldsfromkintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||
def deletefieldsfromkintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
||||
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/form/fields.json"
|
||||
params = {"app":app,"revision":revision,"fields":fields}
|
||||
#r = httpx.delete(url,headers=headers,content=json.dumps(params))
|
||||
r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params))
|
||||
return r.json()
|
||||
|
||||
def deoployappfromkintone(app:str,revision:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||
def deoployappfromkintone(app:str,revision:str,c:config.KINTONE_ENV):
|
||||
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/deploy.json"
|
||||
data = {"apps":[{"app":app,"revision":revision}],"revert": False}
|
||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||
return r.json
|
||||
|
||||
# 既定項目に含めるアプリのフィールドのみ取得する
|
||||
# スペース、枠線、ラベルを含まない
|
||||
def getfieldsfromkintone(app:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/form/fields.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
# フォームに配置するフィールドのみ取得する
|
||||
# スペース、枠線、ラベルも含める
|
||||
def getformfromkintone(app:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/form.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
|
||||
def merge_kintone_fields(fields_response: dict, form_response: dict) -> dict:
|
||||
fields_properties = fields_response.get('properties', {})
|
||||
form_properties = form_response.get('properties', [])
|
||||
|
||||
merged_properties = {k: v for k, v in fields_properties.items()}
|
||||
|
||||
for index, form_field in enumerate(form_properties):
|
||||
code = form_field.get('code')
|
||||
if code:
|
||||
if code and code not in merged_properties:
|
||||
merged_properties[code] = form_field
|
||||
else:
|
||||
element_id = form_field.get('elementId')
|
||||
if element_id:
|
||||
key = element_id
|
||||
form_field['code']=element_id
|
||||
form_field['label']=form_field.get('type')
|
||||
# else:
|
||||
# key = f"{form_field.get('type')}_{index}"
|
||||
merged_properties[key] = form_field
|
||||
|
||||
merged_response = {
|
||||
'revision': fields_response.get('revision', ''),
|
||||
'properties': merged_properties
|
||||
}
|
||||
|
||||
return merged_response
|
||||
def getfieldsfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/form/fields.json"
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
def analysefields(excel,kintone):
|
||||
updatefields={}
|
||||
@@ -286,10 +245,10 @@ def analysefields(excel,kintone):
|
||||
|
||||
return {"update":updatefields,"add":addfields,"del":delfields}
|
||||
|
||||
def getprocessfromkintone(app:str,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
def getprocessfromkintone(app:str,c:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
params = {"app":app}
|
||||
url = f"{env.BASE_URL}{config.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)
|
||||
return r.json()
|
||||
|
||||
@@ -374,95 +333,49 @@ def getkintoneorgs(c:config.KINTONE_ENV):
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
def uploadkintonefiles(file,env:config.KINTONE_ENV):
|
||||
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:env.API_V1_AUTH_VALUE}
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
data ={'name':'file','filename':os.path.basename(file)}
|
||||
url = f"{env.BASE_URL}/k/v1/file.json"
|
||||
r = httpx.post(url,headers=headers,data=data,files=upload_files)
|
||||
#{"name":data['filename'],'fileKey':r['fileKey']}
|
||||
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,env:config.KINTONE_ENV):
|
||||
def updateappjscss(app,uploads,c:config.KINTONE_ENV):
|
||||
dsjs = []
|
||||
dscss = []
|
||||
#mobile側
|
||||
mbjs = []
|
||||
mbcss = []
|
||||
customize = getappcustomize(app, env)
|
||||
current_js = customize['desktop'].get('js', [])
|
||||
current_css = customize['desktop'].get('css', [])
|
||||
current_mobile_js = customize['mobile'].get('js', [])
|
||||
current_mobile_css = customize['mobile'].get('css', [])
|
||||
current_js = [item for item in current_js if not (item.get('type') == 'URL' and item.get('url', '').endswith('alc_runtime.js'))]
|
||||
for upload in uploads:
|
||||
for key in upload:
|
||||
filename = os.path.basename(key)
|
||||
if key.endswith('.js'):
|
||||
existing_js = next((item for item in current_js
|
||||
if item.get('type') == 'FILE' and item['file']['name'] == filename
|
||||
), None)
|
||||
if existing_js:
|
||||
current_js = [item for item in current_js if item.get('type') == 'URL' or item['file'].get('name') != filename]
|
||||
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||
if (key.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
||||
dsjs.append({'type':'URL','url':config.DEPLOY_JS_URL})
|
||||
else:
|
||||
if (key.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
||||
dsjs.append({'type':'URL','url':config.DEPLOY_JS_URL})
|
||||
else:
|
||||
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||
elif key.endswith('.css'):
|
||||
existing_css = next((item for item in current_css
|
||||
if item.get('type') == 'FILE' and item['file']['name'] == filename
|
||||
), None)
|
||||
if existing_css:
|
||||
current_css = [item for item in current_css if item.get('type') == 'URL' or item['file'].get('name') != filename]
|
||||
dscss.append({'type': 'FILE', 'file': {'fileKey': upload[key]}})
|
||||
else:
|
||||
dscss.append({'type': 'FILE', 'file': {'fileKey': upload[key]}})
|
||||
#現在のJSとCSSがdsjsに追加する
|
||||
dsjs.extend(current_js)
|
||||
dscss.extend(current_css)
|
||||
mbjs.extend(current_mobile_js)
|
||||
mbcss.extend(current_mobile_css)
|
||||
|
||||
dscss.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||
ds ={'js':dsjs,'css':dscss}
|
||||
mb ={'js':mbjs,'css':mbcss}
|
||||
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb,'revision':customize["revision"]}
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||
print(json.dumps(data))
|
||||
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()
|
||||
|
||||
#kintone カスタマイズ情報
|
||||
def getappcustomize(app,env:config.KINTONE_ENV):
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||
params = {"app":app}
|
||||
r = httpx.get(url,headers=headers,params=params)
|
||||
return r.json()
|
||||
|
||||
|
||||
def getTempPath(filename):
|
||||
scriptdir = Path(__file__).resolve().parent
|
||||
rootdir = scriptdir.parent.parent.parent.parent
|
||||
fpath = os.path.join(rootdir,"Temp",filename)
|
||||
return fpath
|
||||
|
||||
def createappjs(domain_url,app):
|
||||
def createappjs(domainid,app):
|
||||
db = SessionLocal()
|
||||
flows = get_flows_by_app(db,domain_url,app)
|
||||
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")
|
||||
fpath = getTempPath(f"alc_setting_{app}.js")
|
||||
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)
|
||||
@@ -521,7 +434,7 @@ async def upload(request:Request,files:t.List[UploadFile] = File(...)):
|
||||
return {"files": [file.filename for file in files]}
|
||||
|
||||
@r.post("/updatejscss")
|
||||
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)):
|
||||
try:
|
||||
jscs=[]
|
||||
for file in files:
|
||||
@@ -542,87 +455,66 @@ async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env
|
||||
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")
|
||||
async def app(request:Request,app:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
async def app(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app.json"
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app.json"
|
||||
params ={"id":app}
|
||||
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({env.DOMAIN_NAME}->{app}):",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,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
async def allapps(request:Request,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/apps.json"
|
||||
offset = 0
|
||||
limit = 100
|
||||
all_apps = []
|
||||
|
||||
while True:
|
||||
r = httpx.get(f"{url}?limit={limit}&offset={offset}", headers=headers)
|
||||
json_data = r.json()
|
||||
apps = json_data.get("apps",[])
|
||||
all_apps.extend(apps)
|
||||
if len(apps)<limit:
|
||||
break
|
||||
offset += limit
|
||||
return {"apps": all_apps}
|
||||
|
||||
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({env.DOMAIN_NAME}):", e)
|
||||
raise APIException('kintone:allapps',request.url._url, f"Error occurred while get allapps({c.DOMAIN_NAM}):",e)
|
||||
|
||||
@r.get("/appfields")
|
||||
async def appfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
async def appfields(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
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_NAME}->{app}):",e)
|
||||
|
||||
@r.get("/allfields")
|
||||
async def allfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
try:
|
||||
field_resp = getfieldsfromkintone(app,env)
|
||||
form_resp = getformfromkintone(app,env)
|
||||
return merge_kintone_fields(field_resp,form_resp)
|
||||
except Exception as e:
|
||||
raise APIException('kintone:allfields',request.url._url, f"Error occurred while get form fileds({env.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAM}->{app}):",e)
|
||||
|
||||
@r.get("/appprocess")
|
||||
async def appprocess(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||
async def appprocess(request:Request,app:str,env = Depends(getkintoneenv)):
|
||||
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_NAME}->{app}):",e)
|
||||
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAM}->{app}):",e)
|
||||
|
||||
@r.get("/alljscss")
|
||||
async def alljscs(request:Request,app:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
async def alljscs(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/app/customize.json"
|
||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/customize.json"
|
||||
params = {"app":app}
|
||||
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({env.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({c.DOMAIN_NAM}->{app}):",e)
|
||||
|
||||
@r.post("/createapp",)
|
||||
async def createapp(request:Request,name:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
async def createapp(request:Request,name:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||
try:
|
||||
headers={config.API_V1_AUTH_KEY:env.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}
|
||||
url = f"{env.BASE_URL}{config.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))
|
||||
result = r.json()
|
||||
if result.get("app") != None:
|
||||
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||
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({env.DOMAIN_NAME}->{name}):",e)
|
||||
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({c.DOMAIN_NAM}->{name}):",e)
|
||||
|
||||
|
||||
@r.post("/createappfromexcel",)
|
||||
@@ -751,7 +643,7 @@ async def updateprocessfromexcel(request:Request,app:str,env = Depends(getkinton
|
||||
if deploy:
|
||||
result = deoployappfromkintone(app,revision,env)
|
||||
except Exception as e:
|
||||
raise APIException('kintone:updateprocessfromexcel',request.url._url, f"Error occurred while update process ({env.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:updateprocessfromexcel',request.url._url, f"Error occurred while update process ({env.DOMAIN_NAM}->{app}):",e)
|
||||
|
||||
return result
|
||||
|
||||
@@ -761,17 +653,15 @@ async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Dep
|
||||
try:
|
||||
jscs=[]
|
||||
files=[]
|
||||
files.append(createappjs(env.BASE_URL, app))
|
||||
files.append(getTempPath('alc_runtime.js'))
|
||||
files.append(getTempPath('alc_runtime.css'))
|
||||
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:
|
||||
print(upload)
|
||||
jscs.append({ file :upload['fileKey']})
|
||||
appjscs = updateappjscss(app,jscs,env)
|
||||
if appjscs.get("revision") != None:
|
||||
deoployappfromkintone(app,appjscs["revision"],env)
|
||||
return appjscs
|
||||
except Exception as e:
|
||||
raise APIException('kintone:createjstokintone',request.url._url, f"Error occurred while create js ({env.DOMAIN_NAME}->{app}):",e)
|
||||
raise APIException('kintone:createjstokintone',request.url._url, f"Error occurred while create js ({env.DOMAIN_NAM}->{app}):",e)
|
||||
|
||||
@@ -1,88 +1,14 @@
|
||||
from http import HTTPStatus
|
||||
from fastapi import Query, Request,Depends, APIRouter, UploadFile,HTTPException,File
|
||||
from fastapi.responses import JSONResponse
|
||||
# from app.core.operation import log_operation
|
||||
from fastapi import Request,Depends, APIRouter, UploadFile,HTTPException,File
|
||||
from app.db import Base,engine
|
||||
from app.db.session import get_db
|
||||
from app.db.crud import *
|
||||
from app.db.schemas import *
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
from app.core.auth import get_current_active_user,get_current_user
|
||||
from app.core.apiexception import APIException
|
||||
|
||||
import httpx
|
||||
import app.core.config as config
|
||||
|
||||
platform_router = r = APIRouter()
|
||||
|
||||
|
||||
@r.get(
|
||||
"/apps",
|
||||
response_model=List[AppList],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def apps_list(
|
||||
request: Request,
|
||||
user = Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
|
||||
domain = get_activedomain(db, user.id)
|
||||
platformapps = get_apps(db,domain.url)
|
||||
kintoneevn = config.KINTONE_ENV(domain)
|
||||
headers={config.API_V1_AUTH_KEY:kintoneevn.API_V1_AUTH_VALUE}
|
||||
url = f"{kintoneevn.BASE_URL}{config.API_V1_STR}/apps.json"
|
||||
offset = 0
|
||||
limit = 100
|
||||
all_apps = []
|
||||
|
||||
while True:
|
||||
r = httpx.get(f"{url}?limit={limit}&offset={offset}", headers=headers)
|
||||
json_data = r.json()
|
||||
apps = json_data.get("apps",[])
|
||||
all_apps.extend(apps)
|
||||
if len(apps)<limit:
|
||||
break
|
||||
offset += limit
|
||||
|
||||
kintone_apps_dict = {app['appId']: app for app in all_apps}
|
||||
filtered_apps = []
|
||||
for papp in platformapps:
|
||||
if papp.appid in kintone_apps_dict:
|
||||
papp.appname = kintone_apps_dict[papp.appid]["name"]
|
||||
filtered_apps.append(papp)
|
||||
return filtered_apps
|
||||
except Exception as e:
|
||||
raise APIException('platform:apps',request.url._url,f"Error occurred while get apps:",e)
|
||||
|
||||
@r.post("/apps", response_model=AppList, response_model_exclude_none=True)
|
||||
async def apps_update(
|
||||
request: Request,
|
||||
app: AppVersion,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return update_appversion(db, app,user.id)
|
||||
except Exception as e:
|
||||
raise APIException('platform:apps',request.url._url,f"Error occurred while get create app :",e)
|
||||
|
||||
@r.delete(
|
||||
"/apps/{domainurl}/{appid}", response_model_exclude_none=True
|
||||
)
|
||||
async def apps_delete(
|
||||
request: Request,
|
||||
domainurl:str,
|
||||
appid: str,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return delete_apps(db, domainurl,appid)
|
||||
except Exception as e:
|
||||
raise APIException('platform:apps',request.url._url,f"Error occurred while delete apps({domainurl}:{appid}):",e)
|
||||
|
||||
@r.get(
|
||||
"/appsettings/{id}",
|
||||
response_model=App,
|
||||
@@ -197,13 +123,13 @@ async def flow_details(
|
||||
async def flow_list(
|
||||
request: Request,
|
||||
appid: str,
|
||||
user=Depends(get_current_active_user),
|
||||
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.url, appid)
|
||||
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)
|
||||
@@ -212,13 +138,13 @@ async def flow_list(
|
||||
@r.post("/flow", response_model=Flow, response_model_exclude_none=True)
|
||||
async def flow_create(
|
||||
request: Request,
|
||||
flow: FlowIn,
|
||||
user=Depends(get_current_active_user),
|
||||
flow: FlowBase,
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domain = get_activedomain(db, user.id)
|
||||
return create_flow(db, domain.url, flow)
|
||||
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)
|
||||
|
||||
@@ -228,14 +154,11 @@ async def flow_create(
|
||||
)
|
||||
async def flow_edit(
|
||||
request: Request,
|
||||
flowid: str,
|
||||
flow: FlowIn,
|
||||
user=Depends(get_current_active_user),
|
||||
flow: FlowBase,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domain = get_activedomain(db, user.id)
|
||||
return edit_flow(db,domain.url, flow,user.id)
|
||||
return edit_flow(db, flow)
|
||||
except Exception as e:
|
||||
raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e)
|
||||
|
||||
@@ -273,11 +196,10 @@ async def domain_details(
|
||||
async def domain_create(
|
||||
request: Request,
|
||||
domain: DomainBase,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return create_domain(db, domain,user.id)
|
||||
return create_domain(db, domain)
|
||||
except Exception as e:
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
|
||||
|
||||
@@ -288,11 +210,10 @@ async def domain_create(
|
||||
async def domain_edit(
|
||||
request: Request,
|
||||
domain: DomainBase,
|
||||
user=Depends(get_current_active_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
return edit_domain(db, domain,user.id)
|
||||
return edit_domain(db, domain)
|
||||
except Exception as e:
|
||||
raise APIException('platform:domain',request.url._url,f"Error occurred while edit domain:",e)
|
||||
|
||||
@@ -312,17 +233,16 @@ async def domain_delete(
|
||||
|
||||
@r.get(
|
||||
"/domain",
|
||||
# response_model=List[Domain],
|
||||
response_model=List[Domain],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def userdomain_details(
|
||||
request: Request,
|
||||
userId: Optional[int] = Query(None, alias="userId"),
|
||||
user=Depends(get_current_active_user),
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domains = get_domain(db, userId if userId is not None else user.id)
|
||||
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)
|
||||
@@ -334,7 +254,7 @@ async def userdomain_details(
|
||||
async def create_userdomain(
|
||||
request: Request,
|
||||
userid: int,
|
||||
domainids:List[int] ,
|
||||
domainids:list,
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
@@ -365,15 +285,11 @@ async def userdomain_delete(
|
||||
)
|
||||
async def get_useractivedomain(
|
||||
request: Request,
|
||||
userId: Optional[int] = Query(None, alias="userId"),
|
||||
user=Depends(get_current_active_user),
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
# domain = get_activedomain(db, user.id)
|
||||
domain = get_activedomain(db, userId if userId is not None else user.id)
|
||||
if domain is None:
|
||||
return JSONResponse(content=None,status_code=HTTPStatus.OK)
|
||||
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)
|
||||
@@ -385,12 +301,11 @@ async def get_useractivedomain(
|
||||
async def update_activeuserdomain(
|
||||
request: Request,
|
||||
domainid:int,
|
||||
userId: Optional[int] = Query(None, alias="userId"),
|
||||
user=Depends(get_current_active_user),
|
||||
user=Depends(get_current_user),
|
||||
db=Depends(get_db),
|
||||
):
|
||||
try:
|
||||
domain = active_userdomain(db, userId if userId is not None else user.id,domainid)
|
||||
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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, Request, Depends, Response, Security, encoders
|
||||
from fastapi import APIRouter, Request, Depends, Response, encoders
|
||||
import typing as t
|
||||
|
||||
from app.db.session import get_db
|
||||
@@ -8,11 +8,9 @@ from app.db.crud import (
|
||||
create_user,
|
||||
delete_user,
|
||||
edit_user,
|
||||
assign_userrole,
|
||||
get_roles,
|
||||
)
|
||||
from app.db.schemas import UserCreate, UserEdit, User, UserOut,Role
|
||||
from app.core.auth import get_current_user,get_current_active_user, get_current_active_superuser
|
||||
from app.db.schemas import UserCreate, UserEdit, User, UserOut
|
||||
from app.core.auth import get_current_active_user, get_current_active_superuser
|
||||
|
||||
users_router = r = APIRouter()
|
||||
|
||||
@@ -25,14 +23,14 @@ users_router = r = APIRouter()
|
||||
async def users_list(
|
||||
response: Response,
|
||||
db=Depends(get_db),
|
||||
current_user=Depends(get_current_active_user),
|
||||
current_user=Depends(get_current_active_superuser),
|
||||
):
|
||||
"""
|
||||
Get all users
|
||||
"""
|
||||
users = get_users(db,current_user.is_superuser)
|
||||
users = get_users(db)
|
||||
# This is necessary for react-admin to work
|
||||
#response.headers["Content-Range"] = f"0-9/{len(users)}"
|
||||
response.headers["Content-Range"] = f"0-9/{len(users)}"
|
||||
return users
|
||||
|
||||
|
||||
@@ -107,30 +105,3 @@ async def user_delete(
|
||||
Delete existing user
|
||||
"""
|
||||
return delete_user(db, user_id)
|
||||
|
||||
|
||||
@r.post("/userrole",
|
||||
response_model=User,
|
||||
response_model_exclude_none=True,)
|
||||
async def assign_role(
|
||||
request: Request,
|
||||
userid:int,
|
||||
roles:t.List[int],
|
||||
db=Depends(get_db)
|
||||
):
|
||||
|
||||
return assign_userrole(db,userid,roles)
|
||||
|
||||
|
||||
@r.get(
|
||||
"/roles",
|
||||
response_model=t.List[Role],
|
||||
response_model_exclude_none=True,
|
||||
)
|
||||
async def roles_list(
|
||||
response: Response,
|
||||
db=Depends(get_db),
|
||||
current_user=Security(get_current_active_user, scopes=["role_list"]),
|
||||
):
|
||||
roles = get_roles(db)
|
||||
return roles
|
||||
|
||||
@@ -1,35 +1,22 @@
|
||||
from fastapi import HTTPException, status
|
||||
import httpx
|
||||
from app.db.schemas import ErrorCreate
|
||||
from app.db.session import SessionLocal
|
||||
from app.db.crud import create_log
|
||||
|
||||
class APIException(Exception):
|
||||
def __init__(self, location: str, title: str, content: str, e: Exception):
|
||||
self.detail = str(e)
|
||||
self.status_code = 500
|
||||
if isinstance(e,httpx.HTTPStatusError):
|
||||
try:
|
||||
error_response = e.response.json()
|
||||
self.detail = error_response.get('message', self.detail)
|
||||
self.status_code = e.response.status_code
|
||||
content += self.detail
|
||||
except ValueError:
|
||||
pass
|
||||
elif hasattr(e, 'detail'):
|
||||
self.detail = e.detail
|
||||
self.status_code = e.status_code if hasattr(e, 'status_code') else 500
|
||||
content += e.detail
|
||||
else:
|
||||
self.detail = str(e)
|
||||
self.status_code = 500
|
||||
content += str(e)
|
||||
|
||||
if len(content) > 5000:
|
||||
content = content[:5000]
|
||||
|
||||
self.error = ErrorCreate(location=location, title=title, content=content)
|
||||
super().__init__(self.error)
|
||||
|
||||
def __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()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from fastapi.security import SecurityScopes
|
||||
import jwt
|
||||
from fastapi import Depends, HTTPException, Request, Security, status
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from jwt import PyJWTError
|
||||
|
||||
from app.db import models, schemas, session
|
||||
@@ -8,7 +7,7 @@ from app.db.crud import get_user_by_email, create_user,get_user
|
||||
from app.core import security
|
||||
|
||||
|
||||
async def get_current_user(security_scopes: SecurityScopes,
|
||||
async def get_current_user(
|
||||
db=Depends(session.get_db), token: str = Depends(security.oauth2_scheme)
|
||||
):
|
||||
credentials_exception = HTTPException(
|
||||
@@ -17,21 +16,13 @@ async def get_current_user(security_scopes: SecurityScopes,
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
try:
|
||||
|
||||
payload = jwt.decode(
|
||||
token, security.SECRET_KEY, algorithms=[security.ALGORITHM]
|
||||
)
|
||||
id: int = payload.get("sub")
|
||||
if id is None:
|
||||
raise credentials_exception
|
||||
|
||||
permissions: str = payload.get("permissions")
|
||||
if not permissions =="ALL":
|
||||
for scope in security_scopes.scopes:
|
||||
if scope not in permissions.split(";"):
|
||||
raise HTTPException(
|
||||
status_code=403, detail="The user doesn't have enough privileges"
|
||||
)
|
||||
token_data = schemas.TokenData(id = id, permissions=permissions)
|
||||
except PyJWTError:
|
||||
raise credentials_exception
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
import os
|
||||
import base64
|
||||
|
||||
|
||||
PROJECT_NAME = "KintoneAppBuilder"
|
||||
|
||||
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/dev"
|
||||
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://kabAdmin:P%40ssw0rd!@kintonetooldb.postgres.database.azure.com/dev_v2"
|
||||
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/test"
|
||||
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@ktune-prod-db.postgres.database.azure.com/postgres"
|
||||
#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"
|
||||
|
||||
API_V1_STR = "/k/v1"
|
||||
|
||||
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
||||
|
||||
DEPLOY_MODE = "PROD" #DEV,PROD
|
||||
DEPLOY_MODE = "DEV" #DEV,PROD
|
||||
|
||||
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_PROPERTY=['label','code','type','required','unique','maxValue','minValue','maxLength','minLength','defaultValue','defaultNowValue','options','expression','hideExpression','digit','protocol','displayScale','unit','unitPosition']
|
||||
|
||||
KINTONE_PSW_CRYPTO_KEY=bytes.fromhex("53 6c 93 bd 48 ad b5 c0 93 df a1 27 25 a1 a3 32 a2 03 3b a0 27 1f 51 dc 20 0e 6c d7 be fc fb ea")
|
||||
|
||||
class KINTONE_ENV:
|
||||
|
||||
|
||||
BASE_URL = ""
|
||||
|
||||
API_V1_AUTH_VALUE = ""
|
||||
@@ -39,4 +36,4 @@ class KINTONE_ENV:
|
||||
self.DOMAIN_ID=domain.id
|
||||
self.BASE_URL = domain.url
|
||||
self.KINTONE_USER = domain.kintoneuser
|
||||
self.API_V1_AUTH_VALUE = base64.b64encode(bytes(f"{domain.kintoneuser}:{domain.decrypt_kintonepwd()}","utf-8"))
|
||||
self.API_V1_AUTH_VALUE = base64.b64encode(bytes(f"{domain.kintoneuser}:{domain.kintonepwd}","utf-8"))
|
||||
@@ -2,10 +2,6 @@ import jwt
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from passlib.context import CryptContext
|
||||
from datetime import datetime, timedelta
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
|
||||
import os
|
||||
import base64
|
||||
from app.core import config
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token")
|
||||
|
||||
@@ -33,32 +29,3 @@ def create_access_token(*, data: dict, expires_delta: timedelta = None):
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
||||
def chacha20Encrypt(plaintext:str, key=config.KINTONE_PSW_CRYPTO_KEY):
|
||||
if plaintext is None or plaintext == '':
|
||||
return None
|
||||
nonce = os.urandom(16)
|
||||
algorithm = algorithms.ChaCha20(key, nonce)
|
||||
cipher = Cipher(algorithm, mode=None)
|
||||
encryptor = cipher.encryptor()
|
||||
ciphertext = encryptor.update(plaintext.encode('utf-8')) + encryptor.finalize()
|
||||
return base64.b64encode(nonce +'𒀸'.encode('utf-8')+ ciphertext).decode('utf-8')
|
||||
|
||||
def chacha20Decrypt(encoded_str:str, key=config.KINTONE_PSW_CRYPTO_KEY):
|
||||
try:
|
||||
decoded_data = base64.b64decode(encoded_str)
|
||||
if len(decoded_data) < 18:
|
||||
return encoded_str
|
||||
special_char = decoded_data[16:20]
|
||||
if special_char != '𒀸'.encode('utf-8'):
|
||||
return encoded_str
|
||||
nonce = decoded_data[:16]
|
||||
ciphertext = decoded_data[20:]
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
return encoded_str
|
||||
algorithm = algorithms.ChaCha20(key, nonce)
|
||||
cipher = Cipher(algorithm, mode=None)
|
||||
decryptor = cipher.decryptor()
|
||||
plaintext_bytes = decryptor.update(ciphertext) + decryptor.finalize()
|
||||
return plaintext_bytes.decode('utf-8')
|
||||
@@ -1,11 +1,10 @@
|
||||
from datetime import datetime
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_
|
||||
import typing as t
|
||||
|
||||
from . import models, schemas
|
||||
from app.core.security import chacha20Decrypt, get_password_hash
|
||||
from app.core.security import get_password_hash
|
||||
|
||||
|
||||
def get_user(db: Session, user_id: int):
|
||||
@@ -20,12 +19,9 @@ def get_user_by_email(db: Session, email: str) -> schemas.UserBase:
|
||||
|
||||
|
||||
def get_users(
|
||||
db: Session, super:bool
|
||||
db: Session, skip: int = 0, limit: int = 100
|
||||
) -> t.List[schemas.UserOut]:
|
||||
if super:
|
||||
return db.query(models.User).all()
|
||||
else:
|
||||
return db.query(models.User).filter(models.User.is_superuser == False)
|
||||
return db.query(models.User).offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
def create_user(db: Session, user: schemas.UserCreate):
|
||||
@@ -74,80 +70,6 @@ def edit_user(
|
||||
return db_user
|
||||
|
||||
|
||||
def get_roles(
|
||||
db: Session
|
||||
) -> t.List[schemas.Role]:
|
||||
return db.query(models.Role).all()
|
||||
|
||||
def assign_userrole( db: Session, user_id: int, roles: t.List[int]):
|
||||
db_user = db.query(models.User).get(user_id)
|
||||
if db_user:
|
||||
for role in db_user.roles:
|
||||
db_user.roles.remove(role)
|
||||
for roleid in roles:
|
||||
role = db.query(models.Role).get(roleid)
|
||||
if role:
|
||||
db_user.roles.append(role)
|
||||
db.commit()
|
||||
db.refresh(db_user)
|
||||
return db_user
|
||||
|
||||
def get_apps(
|
||||
db: Session,
|
||||
domainurl:str
|
||||
) -> t.List[schemas.AppList]:
|
||||
return db.query(models.App).filter(models.App.domainurl == domainurl).all()
|
||||
|
||||
def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int):
|
||||
db_app = db.query(models.App).filter(and_(models.App.domainurl == appedit.domainurl,models.App.appid == appedit.appid)).first()
|
||||
if not db_app:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="User not found")
|
||||
|
||||
db_app.version = db_app.version + 1
|
||||
appversion = models.AppVersion(
|
||||
domainurl = appedit.domainurl,
|
||||
appid=appedit.appid,
|
||||
appname=db_app.appname,
|
||||
version = db_app.version,
|
||||
versionname = appedit.versionname,
|
||||
comment = appedit.comment,
|
||||
updateuserid = userid,
|
||||
createuserid = userid
|
||||
)
|
||||
db.add(appversion)
|
||||
db.add(db_app)
|
||||
|
||||
flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == appedit.domainurl,models.App.appid == appedit.appid))
|
||||
for flow in flows:
|
||||
db_flowhistory = models.FlowHistory(
|
||||
flowid = flow.flowid,
|
||||
appid = flow.appid,
|
||||
eventid = flow.eventid,
|
||||
domainurl = flow.domainurl,
|
||||
name = flow.name,
|
||||
content = flow.content,
|
||||
createuser = userid,
|
||||
version = db_app.version,
|
||||
updateuserid = userid,
|
||||
createuserid = userid
|
||||
)
|
||||
db.add(db_flowhistory)
|
||||
|
||||
db.commit()
|
||||
db.refresh(db_app)
|
||||
return db_app
|
||||
|
||||
def delete_apps(db: Session, domainurl: str,appid: str ):
|
||||
db_app = db.query(models.App).filter(and_(models.App.domainurl == domainurl,models.App.appid ==appid)).first()
|
||||
if not db_app:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="App not found")
|
||||
db.delete(db_app)
|
||||
db_flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid ==appid))
|
||||
for flow in db_flows:
|
||||
db.delete(flow)
|
||||
db.commit()
|
||||
return db_app
|
||||
|
||||
def get_appsetting(db: Session, id: int):
|
||||
app = db.query(models.AppSetting).get(id)
|
||||
if not app:
|
||||
@@ -203,28 +125,16 @@ def get_actions(db: Session):
|
||||
return actions
|
||||
|
||||
|
||||
def create_flow(db: Session, domainurl: str, flow: schemas.FlowIn,userid:int):
|
||||
def create_flow(db: Session, domainid: int, flow: schemas.FlowBase):
|
||||
db_flow = models.Flow(
|
||||
flowid=flow.flowid,
|
||||
appid=flow.appid,
|
||||
eventid=flow.eventid,
|
||||
domainurl=domainurl,
|
||||
domainid=domainid,
|
||||
name=flow.name,
|
||||
content=flow.content,
|
||||
createuserid = userid,
|
||||
updateuserid = userid
|
||||
content=flow.content
|
||||
)
|
||||
db.add(db_flow)
|
||||
db_app = db.query(models.App).filter(and_(models.App.domainurl == domainurl,models.App.appid == flow.appid)).first()
|
||||
if not db_app:
|
||||
db_app = models.App(
|
||||
domainurl = domainurl,
|
||||
appid=flow.appid,
|
||||
appname=flow.appname,
|
||||
version = 0,
|
||||
createuserid= userid,
|
||||
updateuserid = userid
|
||||
)
|
||||
db.commit()
|
||||
db.refresh(db_flow)
|
||||
return db_flow
|
||||
@@ -239,20 +149,16 @@ def delete_flow(db: Session, flowid: str):
|
||||
|
||||
|
||||
def edit_flow(
|
||||
db: Session, domainurl: str, flow: schemas.FlowIn,userid:int
|
||||
db: Session, flow: schemas.FlowBase
|
||||
) -> schemas.Flow:
|
||||
db_flow = get_flow(db, flow.flowid)
|
||||
if not db_flow:
|
||||
#見つからない時新規作成
|
||||
return create_flow(db,domainurl,flow,userid)
|
||||
|
||||
db_flow.appid =flow.appid
|
||||
db_flow.eventid=flow.eventid
|
||||
db_flow.domainurl=domainurl
|
||||
db_flow.name=flow.name
|
||||
db_flow.content=flow.content
|
||||
db_flow.updateuserid = userid
|
||||
|
||||
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)
|
||||
@@ -267,30 +173,25 @@ def get_flows(db: Session, flowid: str):
|
||||
|
||||
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")
|
||||
if not flow:
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return flow
|
||||
|
||||
def get_flows_by_app(db: Session,domainurl: str, appid: str):
|
||||
flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid == appid)).all()
|
||||
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,userid:int):
|
||||
domain.encrypt_kintonepwd()
|
||||
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,
|
||||
createuserid = userid,
|
||||
updateuserid = userid
|
||||
kintonepwd=domain.kintonepwd
|
||||
)
|
||||
db.add(db_domain)
|
||||
db.flush()
|
||||
add_userdomain(db,userid,db_domain.id)
|
||||
db.commit()
|
||||
db.refresh(db_domain)
|
||||
return db_domain
|
||||
@@ -305,35 +206,32 @@ def delete_domain(db: Session,id: int):
|
||||
|
||||
|
||||
def edit_domain(
|
||||
db: Session, domain: schemas.DomainBase,userid:int
|
||||
db: Session, domain: schemas.DomainBase
|
||||
) -> schemas.Domain:
|
||||
domain.encrypt_kintonepwd()
|
||||
db_domain = db.query(models.Domain).get(domain.id)
|
||||
if not db_domain:
|
||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
db_domain.tenantid = domain.tenantid
|
||||
db_domain.name=domain.name
|
||||
db_domain.url=domain.url
|
||||
db_domain.kintoneuser=domain.kintoneuser
|
||||
db_domain.kintonepwd = domain.kintonepwd
|
||||
db_domain.updateuserid = userid
|
||||
db_domain.update_time = datetime.now
|
||||
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,domainid:int):
|
||||
user_domain = models.UserDomain(userid = userid, domainid = domainid )
|
||||
db.add(user_domain)
|
||||
return user_domain
|
||||
|
||||
def add_userdomains(db: Session, userid:int,domainids:list[str]):
|
||||
dbCommits = list(map(lambda domainid: models.UserDomain(userid = userid, domainid = domainid ), domainids))
|
||||
db.bulk_save_objects(dbCommits)
|
||||
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()
|
||||
return dbCommits
|
||||
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()
|
||||
@@ -356,35 +254,22 @@ def active_userdomain(db: Session, userid: int,domainid: int):
|
||||
db.commit()
|
||||
return db_userdomains
|
||||
|
||||
def get_activedomain(db: Session, userid: int)-> t.Optional[models.Domain]:
|
||||
user_domains = (db.query(models.Domain,models.UserDomain.active)
|
||||
.join(models.UserDomain,models.UserDomain.domainid == models.Domain.id )
|
||||
.filter(models.UserDomain.userid == userid)
|
||||
.all())
|
||||
db_domain=None
|
||||
if len(user_domains)==1:
|
||||
db_domain = user_domains[0][0];
|
||||
else:
|
||||
db_domain = next((domain for domain,active in user_domains if active),None)
|
||||
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||
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")
|
||||
# for domain in domains:
|
||||
# decrypted_pwd = chacha20Decrypt(domain.kintonepwd)
|
||||
# domain.kintonepwd = decrypted_pwd
|
||||
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")
|
||||
# for domain in domains:
|
||||
# decrypted_pwd = chacha20Decrypt(domain.kintonepwd)
|
||||
# domain.kintonepwd = decrypted_pwd
|
||||
return domains
|
||||
|
||||
def get_events(db: Session):
|
||||
@@ -393,35 +278,9 @@ def get_events(db: Session):
|
||||
raise HTTPException(status_code=404, detail="Data not found")
|
||||
return events
|
||||
|
||||
def get_category(db:Session):
|
||||
categorys=db.query(models.Category).all()
|
||||
return categorys
|
||||
|
||||
def get_eventactions(db: Session,eventid: str):
|
||||
#eveactions = db.query(models.Action).join(models.EventAction,models.EventAction.actionid == models.Action.id ).join(models.Event,models.Event.id == models.EventAction.eventid).filter(models.Event.eventid == eventid).all()
|
||||
#category = get_category(db)
|
||||
blackactions = (
|
||||
db.query(models.EventAction.actionid)
|
||||
.filter(models.EventAction.eventid == eventid)
|
||||
.subquery()
|
||||
)
|
||||
eveactions = (
|
||||
db.query(
|
||||
models.Action.id,
|
||||
models.Action.name,
|
||||
models.Action.title,
|
||||
models.Action.subtitle,
|
||||
models.Action.outputpoints,
|
||||
models.Action.property,
|
||||
models.Action.categoryid,
|
||||
models.Action.nosort,
|
||||
models.Category.categoryname)
|
||||
.join(models.Category,models.Category.id == models.Action.categoryid)
|
||||
.filter(models.Action.id.notin_(blackactions))
|
||||
.order_by(models.Category.nosort,models.Action.nosort)
|
||||
.all()
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey,Table
|
||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey
|
||||
from sqlalchemy.ext.declarative import as_declarative
|
||||
from sqlalchemy.orm import relationship
|
||||
from datetime import datetime
|
||||
from app.db.session import Base
|
||||
from app.core.security import chacha20Decrypt
|
||||
|
||||
@as_declarative()
|
||||
class Base:
|
||||
@@ -11,21 +8,6 @@ class Base:
|
||||
create_time = Column(DateTime, default=datetime.now)
|
||||
update_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
|
||||
userrole = Table(
|
||||
"userrole",
|
||||
Base.metadata,
|
||||
Column("userid",Integer,ForeignKey("user.id")),
|
||||
Column("roleid",Integer,ForeignKey("role.id")),
|
||||
)
|
||||
|
||||
rolepermission = Table(
|
||||
"rolepermission",
|
||||
Base.metadata,
|
||||
Column("roleid",Integer,ForeignKey("role.id")),
|
||||
Column("permissionid",Integer,ForeignKey("permission.id")),
|
||||
)
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "user"
|
||||
|
||||
@@ -35,52 +17,6 @@ class User(Base):
|
||||
hashed_password = Column(String(200), nullable=False)
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_superuser = Column(Boolean, default=False)
|
||||
roles = relationship("Role",secondary=userrole,back_populates="users")
|
||||
|
||||
|
||||
class Role(Base):
|
||||
__tablename__ = "role"
|
||||
|
||||
name = Column(String(100))
|
||||
description = Column(String(255))
|
||||
users = relationship("User",secondary=userrole,back_populates="roles")
|
||||
permissions = relationship("Permission",secondary=rolepermission,back_populates="roles")
|
||||
|
||||
class Permission(Base):
|
||||
__tablename__ = "permission"
|
||||
|
||||
menu = Column(String(100))
|
||||
function = Column(String(255))
|
||||
privilege = Column(String(100))
|
||||
roles = relationship("Role",secondary=rolepermission,back_populates="permissions")
|
||||
|
||||
|
||||
class App(Base):
|
||||
__tablename__ = "app"
|
||||
|
||||
domainurl = Column(String(200), nullable=False)
|
||||
appname = Column(String(200), nullable=False)
|
||||
appid = Column(String(100), index=True, nullable=False)
|
||||
version = Column(Integer)
|
||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
class AppVersion(Base):
|
||||
__tablename__ = "appversion"
|
||||
|
||||
domainurl = Column(String(200), nullable=False)
|
||||
appname = Column(String(200), nullable=False)
|
||||
appid = Column(String(100), index=True, nullable=False)
|
||||
version = Column(Integer)
|
||||
versionname = Column(String(200), nullable=False)
|
||||
comment = Column(String(200), nullable=False)
|
||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
|
||||
class AppSetting(Base):
|
||||
__tablename__ = "appsetting"
|
||||
@@ -104,8 +40,6 @@ class Action(Base):
|
||||
subtitle = Column(String(500))
|
||||
outputpoints = Column(String)
|
||||
property = Column(String)
|
||||
categoryid = Column(Integer,ForeignKey("category.id"))
|
||||
nosort = Column(Integer)
|
||||
|
||||
class Flow(Base):
|
||||
__tablename__ = "flow"
|
||||
@@ -113,28 +47,9 @@ class Flow(Base):
|
||||
flowid = Column(String(100), index=True, nullable=False)
|
||||
appid = Column(String(100), index=True, nullable=False)
|
||||
eventid = Column(String(100), index=True, nullable=False)
|
||||
domainurl = Column(String(200))
|
||||
name = Column(String(200))
|
||||
content = Column(String)
|
||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
class FlowHistory(Base):
|
||||
__tablename__ = "flowhistory"
|
||||
|
||||
flowid = Column(String(100), index=True, nullable=False)
|
||||
appid = Column(String(100), index=True, nullable=False)
|
||||
eventid = Column(String(100), index=True, nullable=False)
|
||||
domainurl = Column(String(200))
|
||||
domainid = Column(Integer,ForeignKey("domain.id"))
|
||||
name = Column(String(200))
|
||||
content = Column(String)
|
||||
version = Column(Integer)
|
||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
class Tenant(Base):
|
||||
__tablename__ = "tenant"
|
||||
@@ -153,13 +68,7 @@ class Domain(Base):
|
||||
url = Column(String(200), nullable=False)
|
||||
kintoneuser = Column(String(100), nullable=False)
|
||||
kintonepwd = Column(String(100), nullable=False)
|
||||
def decrypt_kintonepwd(self):
|
||||
decrypted_pwd = chacha20Decrypt(self.kintonepwd)
|
||||
return decrypted_pwd
|
||||
createuserid = Column(Integer,ForeignKey("user.id"))
|
||||
updateuserid = Column(Integer,ForeignKey("user.id"))
|
||||
createuser = relationship('User',foreign_keys=[createuserid])
|
||||
updateuser = relationship('User',foreign_keys=[updateuserid])
|
||||
|
||||
|
||||
class UserDomain(Base):
|
||||
__tablename__ = "userdomain"
|
||||
@@ -181,7 +90,7 @@ class Event(Base):
|
||||
class EventAction(Base):
|
||||
__tablename__ = "eventaction"
|
||||
|
||||
eventid = Column(String(100),ForeignKey("event.eventid"))
|
||||
eventid = Column(Integer,ForeignKey("event.id"))
|
||||
actionid = Column(Integer,ForeignKey("action.id"))
|
||||
|
||||
|
||||
@@ -192,17 +101,6 @@ class ErrorLog(Base):
|
||||
location = Column(String(500))
|
||||
content = Column(String(5000))
|
||||
|
||||
class OperationLog(Base):
|
||||
__tablename__ = "operationlog"
|
||||
|
||||
tenantid = Column(String(100))
|
||||
domainurl = Column(String(200))
|
||||
userid = Column(Integer,ForeignKey("user.id"))
|
||||
operation = Column(String(200))
|
||||
function = Column(String(200))
|
||||
detail = Column(String(200))
|
||||
user = relationship('User')
|
||||
|
||||
class KintoneFormat(Base):
|
||||
__tablename__ = "kintoneformat"
|
||||
|
||||
@@ -212,10 +110,4 @@ class KintoneFormat(Base):
|
||||
typecolumn =Column(Integer)
|
||||
codecolumn =Column(Integer)
|
||||
field = Column(String(5000))
|
||||
trueformat = Column(String(10))
|
||||
|
||||
class Category(Base):
|
||||
__tablename__ = "category"
|
||||
|
||||
categoryname = Column(String(20))
|
||||
nosort = Column(Integer)
|
||||
trueformat = Column(String(10))
|
||||
@@ -2,32 +2,18 @@ from pydantic import BaseModel
|
||||
from datetime import datetime
|
||||
import typing as t
|
||||
|
||||
from app.core.security import chacha20Decrypt, chacha20Encrypt
|
||||
|
||||
class Base(BaseModel):
|
||||
create_time: datetime
|
||||
update_time: datetime
|
||||
|
||||
|
||||
class Permission(BaseModel):
|
||||
id: int
|
||||
menu:str
|
||||
function:str
|
||||
privilege:str
|
||||
|
||||
class Role(BaseModel):
|
||||
id: int
|
||||
name:str
|
||||
description:str
|
||||
permissions:t.List[Permission] = []
|
||||
|
||||
class UserBase(BaseModel):
|
||||
email: str
|
||||
is_active: bool = True
|
||||
is_superuser: bool = False
|
||||
first_name: str = None
|
||||
last_name: str = None
|
||||
roles:t.List[Role] = []
|
||||
|
||||
|
||||
class UserOut(UserBase):
|
||||
pass
|
||||
@@ -41,21 +27,21 @@ class UserCreate(UserBase):
|
||||
is_active:bool
|
||||
is_superuser:bool
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class UserEdit(UserBase):
|
||||
password: t.Optional[str] = None
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
class User(UserBase):
|
||||
id: int
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
@@ -63,20 +49,6 @@ class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
class AppList(Base):
|
||||
domainurl: str
|
||||
appname: str
|
||||
appid:str
|
||||
updateuser: UserOut
|
||||
version:int
|
||||
|
||||
class AppVersion(BaseModel):
|
||||
domainurl: str
|
||||
appname: str
|
||||
versionname: str
|
||||
comment:str
|
||||
appid:str
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
id:int = 0
|
||||
@@ -95,7 +67,7 @@ class AppBase(BaseModel):
|
||||
class App(AppBase):
|
||||
id: int
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
|
||||
@@ -106,7 +78,7 @@ class Kintone(BaseModel):
|
||||
desc: str = None
|
||||
content: str = None
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class Action(BaseModel):
|
||||
@@ -116,17 +88,13 @@ class Action(BaseModel):
|
||||
subtitle: str = None
|
||||
outputpoints: str = None
|
||||
property: str = None
|
||||
categoryid: int = None
|
||||
nosort: int
|
||||
categoryname : str =None
|
||||
class ConfigDict:
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class FlowIn(BaseModel):
|
||||
class FlowBase(BaseModel):
|
||||
flowid: str
|
||||
# domainurl:str
|
||||
appid: str
|
||||
appname:str
|
||||
eventid: str
|
||||
name: str = None
|
||||
content: str = None
|
||||
@@ -136,11 +104,11 @@ class Flow(Base):
|
||||
flowid: str
|
||||
appid: str
|
||||
eventid: str
|
||||
domainurl: str
|
||||
domainid: int
|
||||
name: str = None
|
||||
content: str = None
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class DomainBase(BaseModel):
|
||||
@@ -151,10 +119,6 @@ class DomainBase(BaseModel):
|
||||
kintoneuser: str
|
||||
kintonepwd: str
|
||||
|
||||
def encrypt_kintonepwd(self):
|
||||
encrypted_pwd = chacha20Encrypt(self.kintonepwd)
|
||||
self.kintonepwd = encrypted_pwd
|
||||
|
||||
class Domain(Base):
|
||||
id: int
|
||||
tenantid: str
|
||||
@@ -162,7 +126,8 @@ class Domain(Base):
|
||||
url: str
|
||||
kintoneuser: str
|
||||
kintonepwd: str
|
||||
class ConfigDict:
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class Event(Base):
|
||||
@@ -174,7 +139,7 @@ class Event(Base):
|
||||
mobile: bool
|
||||
eventgroup: bool
|
||||
|
||||
class ConfigDict:
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class ErrorCreate(BaseModel):
|
||||
|
||||
@@ -24,8 +24,4 @@ python -m venv env
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
|
||||
```
|
||||
4. backend 起動
|
||||
```bash
|
||||
uvicorn app.main:app --reload
|
||||
```
|
||||
```
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1,6 +1,3 @@
|
||||
#開発環境
|
||||
#KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
|
||||
#単体テスト環境
|
||||
#KAB_BACKEND_URL="https://kab-backend-unittest.azurewebsites.net/"
|
||||
#ローカル開発環境
|
||||
KAB_BACKEND_URL="http://127.0.0.1:8000/"
|
||||
KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
|
||||
#KAB_BACKEND_URL="http://127.0.0.1:8000/"
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "k-tune",
|
||||
"name": "kintone-automate",
|
||||
"version": "0.2.0",
|
||||
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
|
||||
"productName": "k-tune | kintoneジェネレーター",
|
||||
"productName": "kintone Automate",
|
||||
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -12,15 +12,14 @@
|
||||
"dev": "quasar dev",
|
||||
"dev:local": "set \"LOCAL=true\" && quasar dev",
|
||||
"build": "set \"SOURCE_MAP=false\" && quasar build",
|
||||
"build:dev": "set \"SOURCE_MAP=true\" && quasar build"
|
||||
"build:dev":"set \"SOURCE_MAP=true\" && quasar build"
|
||||
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/extras": "^1.16.4",
|
||||
"@vueuse/core": "^10.9.0",
|
||||
"axios": "^1.4.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"quasar": "^2.6.0",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^3.0.0",
|
||||
|
||||
@@ -94,7 +94,6 @@ module.exports = configure(function (/* ctx */) {
|
||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||
devServer: {
|
||||
// https: true
|
||||
port:9001,
|
||||
open: true, // opens browser window automatically
|
||||
env: { ...dotenv },
|
||||
},
|
||||
|
||||
@@ -2,7 +2,6 @@ import { boot } from 'quasar/wrappers';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import {router} from 'src/router';
|
||||
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
interface ComponentCustomProperties {
|
||||
$axios: AxiosInstance;
|
||||
@@ -16,10 +15,30 @@ declare module '@vue/runtime-core' {
|
||||
// good idea to move this instance creation inside of the
|
||||
// "export default () => {}" function below (which runs individually
|
||||
// for each client)
|
||||
|
||||
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 }) => {
|
||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||
|
||||
app.config.globalProperties.$axios = axios;
|
||||
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
||||
// so you won't necessarily have to import axios in each vue file
|
||||
|
||||
@@ -3,46 +3,20 @@
|
||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||
<q-spinner color="primary" size="3em" />
|
||||
</div>
|
||||
<q-splitter
|
||||
v-model="splitterModel"
|
||||
style="height: 100%"
|
||||
before-class="tab"
|
||||
unit="px"
|
||||
v-else
|
||||
<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"
|
||||
>
|
||||
<template v-slot:before>
|
||||
<q-tabs
|
||||
v-model="tab"
|
||||
vertical
|
||||
active-color="white"
|
||||
indicator-color="primary"
|
||||
active-bg-color="primary"
|
||||
class="bg-grey-2 text-grey-8"
|
||||
dense
|
||||
>
|
||||
<q-tab :name="cate"
|
||||
:label="cate"
|
||||
v-for="(cate,) in categorys"
|
||||
:key="cate"
|
||||
></q-tab>
|
||||
</q-tabs>
|
||||
</template>
|
||||
<template v-slot:after>
|
||||
<q-table row-key="index" :selection="type" v-model:selected="selected" :columns="columns" :rows="actionForTab"
|
||||
class="action-table"
|
||||
flat bordered
|
||||
virtual-scroll
|
||||
:pagination="pagination"
|
||||
:rows-per-page-options="[0]"
|
||||
:filter="filter"></q-table>
|
||||
</template>
|
||||
</q-splitter>
|
||||
</q-table>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import { ref,onMounted,reactive,watchEffect,computed,watch } from 'vue'
|
||||
import { ref,onMounted,reactive } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
|
||||
export default {
|
||||
name: 'actionSelect',
|
||||
@@ -51,74 +25,30 @@ export default {
|
||||
type: String,
|
||||
filter:String
|
||||
},
|
||||
emits:[
|
||||
"clearFilter"
|
||||
],
|
||||
setup(props,{emit}) {
|
||||
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 store = useFlowEditorStore();
|
||||
let actionData =reactive([]);
|
||||
const categorys = ref('');
|
||||
const tab=ref('');
|
||||
const actionForTab=computed(()=>{
|
||||
const rows=[];
|
||||
const actions= props.filter? actionData:actionData.filter(x=>x.categoryname===tab.value);
|
||||
actions.forEach((item,index) =>{
|
||||
rows.push({index,
|
||||
name:item.name,
|
||||
desc:item.title,
|
||||
outputPoints:item.outputpoints,
|
||||
property:item.property});
|
||||
});
|
||||
return rows;
|
||||
});
|
||||
const rows = reactive([])
|
||||
onMounted(async () => {
|
||||
let eventId='';
|
||||
if(store.selectedEvent ){
|
||||
eventId=store.selectedEvent.header!=='DELETABLE'? store.selectedEvent.eventId : store.selectedEvent.parentId;
|
||||
}
|
||||
const res =await api.get(`api/eventactions/${eventId}`);
|
||||
actionData= res.data;
|
||||
const categoryNames = Array.from(new Set(actionData.map(x=>x.categoryname)));
|
||||
categorys.value=categoryNames;
|
||||
tab.value = categoryNames.length>0? categoryNames[0]:'';
|
||||
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;
|
||||
});
|
||||
// watch(props.filter,()=>{
|
||||
// if(props.filter && props.filter!==''){
|
||||
// tab.value='';
|
||||
// }
|
||||
// });
|
||||
watch(tab,()=>{
|
||||
if(tab.value!==''){
|
||||
emit('clearFilter','');
|
||||
}
|
||||
});
|
||||
// watchEffect(()=>{
|
||||
// if(props.filter && props.filter!==''){
|
||||
// tab.value='';
|
||||
// }
|
||||
// if(tab.value!==''){
|
||||
// emit('update:filter','');
|
||||
// }
|
||||
// });
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
selected: ref([]),
|
||||
pagination:ref({
|
||||
rowsPerPage:0
|
||||
}),
|
||||
isLoaded,
|
||||
tab,
|
||||
actionData,
|
||||
categorys,
|
||||
splitterModel: ref(150),
|
||||
actionForTab
|
||||
}
|
||||
},
|
||||
|
||||
@@ -128,6 +58,5 @@ export default {
|
||||
.action-table{
|
||||
min-height: 10vh;
|
||||
max-height: 68vh;
|
||||
min-width: 550px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="q-mb-xs q-ml-md text-primary">アプリ選択</div>
|
||||
|
||||
<div class="q-pa-md row" style="border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px;">
|
||||
<div v-if="selField?.app && !showSelectApp">{{ selField?.app?.name }}</div>
|
||||
<div v-if="selField?.app && !showSelectApp">{{ selField.app?.name }}</div>
|
||||
<q-space />
|
||||
<div>
|
||||
<q-btn outline dense label="選 択" padding="none sm" color="primary" @click="() => {
|
||||
@@ -14,7 +14,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!showSelectApp && selField?.app?.name">
|
||||
<div v-if="!showSelectApp && selField.app?.name">
|
||||
<div>
|
||||
<div class="row q-mb-md">
|
||||
<!-- <div class="col"> -->
|
||||
@@ -31,9 +31,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelectFields="updateSelectFields"
|
||||
:appId="selField?.app?.id" not_page :filter="fieldFilter"
|
||||
:selectedFields="selField.fields" :fieldTypes="fieldTypes"></field-select>
|
||||
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelects="updateItems"
|
||||
:appId="selField.app?.id" not_page :filter="fieldFilter"
|
||||
:selFields="selField.fields"></field-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,7 +41,7 @@
|
||||
</div>
|
||||
|
||||
|
||||
<show-dialog v-model:visible="showSelectApp" name="アプリ選択">
|
||||
<show-dialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeAppDlg">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
@@ -51,14 +51,15 @@
|
||||
</template>
|
||||
|
||||
<AppSelectBox ref="appDlg" name="アプリ" type="single" :filter="filter"
|
||||
:updateSelectApp="updateSelectApp"></AppSelectBox>
|
||||
:updateExternalSelectAppInfo="updateExternalSelectAppInfo"></AppSelectBox>
|
||||
</show-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watchEffect, computed, reactive } from 'vue';
|
||||
import { defineComponent, ref, watchEffect, computed ,reactive} from 'vue';
|
||||
import ShowDialog from './ShowDialog.vue';
|
||||
import FieldSelect from './FieldSelect.vue';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import AppSelectBox from './AppSelectBox.vue';
|
||||
interface IApp {
|
||||
id: string,
|
||||
@@ -84,7 +85,7 @@ export default defineComponent({
|
||||
AppSelectBox,
|
||||
},
|
||||
props: {
|
||||
selectedField: {
|
||||
selectedField: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
@@ -92,24 +93,40 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
}
|
||||
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const appDlg = ref();
|
||||
const fieldDlg = ref();
|
||||
const showSelectApp = ref(false);
|
||||
const selField = reactive(props.selectedField);
|
||||
console.log(props.selectedField);
|
||||
|
||||
const store = useFlowEditorStore();
|
||||
|
||||
const isSelected = computed(() => {
|
||||
return selField !== null && typeof selField === 'object' && ('app' in selField)
|
||||
});
|
||||
|
||||
const updateSelectApp = (newAppinfo: IApp) => {
|
||||
|
||||
const closeAppDlg = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
selField.app = appDlg.value.selected[0];
|
||||
selField.fields = [];
|
||||
showSelectApp.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const closeFieldDialog = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
selField.fields = fieldDlg.value.selected;
|
||||
}
|
||||
};
|
||||
const updateExternalSelectAppInfo = (newAppinfo: IApp) => {
|
||||
selField.app = newAppinfo
|
||||
}
|
||||
|
||||
const updateSelectFields = (newFields: IField[]) => {
|
||||
const updateItems = (newFields: IField[]) => {
|
||||
selField.fields = newFields
|
||||
}
|
||||
|
||||
@@ -118,11 +135,15 @@ export default defineComponent({
|
||||
});
|
||||
|
||||
return {
|
||||
appDlg,
|
||||
fieldDlg,
|
||||
closeAppDlg,
|
||||
closeFieldDialog,
|
||||
showSelectApp,
|
||||
isSelected,
|
||||
updateSelectApp,
|
||||
updateExternalSelectAppInfo,
|
||||
filter: ref(),
|
||||
updateSelectFields,
|
||||
updateItems,
|
||||
fieldFilter: ref(),
|
||||
selField
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</q-field>
|
||||
<q-field stack-label full-width label="アプリの説明">
|
||||
<q-field stack-label full-width label="アプリ説明">
|
||||
<template v-slot:control>
|
||||
<div class="self-center full-width no-outline" tabindex="0">
|
||||
{{ appinfo?.description }}
|
||||
|
||||
@@ -17,58 +17,44 @@
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref, onMounted, reactive, watchEffect, PropType } from 'vue'
|
||||
import { ref, onMounted, reactive, watchEffect } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
interface IAppDisplay {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdate: string;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'AppSelectBox',
|
||||
props: {
|
||||
name: String,
|
||||
type: String,
|
||||
filter: String,
|
||||
filterInitRowsFunc: {
|
||||
type: Function as PropType<(app: IAppDisplay) => boolean>,
|
||||
},
|
||||
updateSelectApp: {
|
||||
updateExternalSelectAppInfo: {
|
||||
type: Function
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const columns = [
|
||||
{ name: 'id', required: true, label: 'ID', align: 'left', field: 'id', sortable: true, sort: (a: string, b: string) => parseInt(a, 10) - parseInt(b, 10) },
|
||||
{ name: '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 = reactive<IAppDisplay[]>([]);
|
||||
const rows: any[] = reactive([]);
|
||||
const selected = ref([])
|
||||
|
||||
watchEffect(()=>{
|
||||
if (selected.value && selected.value[0] && props.updateSelectApp) {
|
||||
props.updateSelectApp(selected.value[0])
|
||||
if (selected.value && selected.value[0] && props.updateExternalSelectAppInfo) {
|
||||
props.updateExternalSelectAppInfo(selected.value[0])
|
||||
}
|
||||
});
|
||||
onMounted(() => {
|
||||
api.get('api/v1/allapps').then(res => {
|
||||
res.data.apps.forEach((item: any) => {
|
||||
const row : IAppDisplay = {
|
||||
rows.push({
|
||||
id: item.appId,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
createdate: dateFormat(item.createdAt)
|
||||
}
|
||||
if (props.filterInitRowsFunc && !props.filterInitRowsFunc(row)) {
|
||||
return;
|
||||
}
|
||||
rows.push(row);
|
||||
});
|
||||
});
|
||||
isLoaded.value = true;
|
||||
});
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<q-stepper v-model="step" ref="stepper" color="primary" animated flat>
|
||||
<q-step :name="1" title="データソースの設定" icon="app_registration" :done="step > 1">
|
||||
<div class="row justify-between items-center">
|
||||
<div>アプリの選択 :</div>
|
||||
<div>
|
||||
<a v-if="data.sourceApp?.name" class="q-mr-xs"
|
||||
:href="data.sourceApp ? `${authStore.currentDomain.kintoneUrl}/k/${data.sourceApp.id}` : ''"
|
||||
target="_blank" title="Kiontoneへ">
|
||||
{{ data.sourceApp?.name }}
|
||||
</a>
|
||||
<div v-else class="text-red">APPを選択してください</div>
|
||||
<q-btn v-if="data.sourceApp?.name" flat color="grey" icon="clear" size="sm" padding="none"
|
||||
@click="clearSelectedApp" />
|
||||
</div>
|
||||
<q-btn outline dense label="変更" padding="xs sm" color="primary" @click="showAppDialog" />
|
||||
</div>
|
||||
|
||||
<!-- フィールド設定部分 -->
|
||||
<template v-if="data.sourceApp?.name">
|
||||
<q-separator class="q-mt-md" />
|
||||
<div class="q-my-md row justify-between items-center">
|
||||
データ階層を設定する :
|
||||
<q-btn icon="add" size="sm" padding="xs" outline color="primary" @click="addRow" />
|
||||
</div>
|
||||
<q-virtual-scroll style="max-height: 13.5rem;" :items="data.fieldList" separator v-slot="{ item, index }">
|
||||
<div class="row justify-between items-center q-my-md">
|
||||
<div>{{ index + 1 }}階層 :</div>
|
||||
<div>{{ item.source?.name }}</div>
|
||||
<q-btn-group outline>
|
||||
<q-btn outline dense label="変更" padding="xs sm" color="primary"
|
||||
@click="() => showFieldDialog(item, 'source')" />
|
||||
<q-btn outline dense label="削除" padding="xs sm" color="primary" @click="() => delRow(index)" />
|
||||
</q-btn-group>
|
||||
</div>
|
||||
</q-virtual-scroll>
|
||||
</template>
|
||||
|
||||
<!-- アプリ選択ダイアログ -->
|
||||
<ShowDialog v-model:visible="data.sourceApp.showSelectApp" name="アプリ選択" @close="closeAppDialog" min-width="50vw"
|
||||
min-height="50vh">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="data.sourceApp.appFilter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="data.sourceApp.appFilter" />
|
||||
</ShowDialog>
|
||||
</q-step>
|
||||
|
||||
<q-step :name="2" title="ドロップダウンフィールドの設定" icon="multiple_stop" :done="step > 2">
|
||||
<div class="row q-pa-sm q-col-gutter-x-sm flex-center">
|
||||
<div class="col-grow row q-col-gutter-x-sm">
|
||||
<div class="col-6">データソース</div>
|
||||
<div class="col-6">ドロップダウンフィールド</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div style="width: 88px; height: 1px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(item) in data.fieldList" :key="item.id" class="row q-pa-sm q-col-gutter-x-sm flex-center">
|
||||
<div class="col-grow row q-col-gutter-x-sm">
|
||||
|
||||
<div class="col-6">{{ item.source.name }}</div>
|
||||
<div class="col-6">{{ item.dropDown?.name }}</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="row justify-end">
|
||||
<q-btn-group outline>
|
||||
<q-btn outline dense label="設定" padding="xs sm" color="primary"
|
||||
@click="() => showFieldDialog(item, 'dropDown')" />
|
||||
<q-btn outline dense label="クリア" padding="xs sm" color="primary"
|
||||
@click="() => item.dropDown = undefined" />
|
||||
</q-btn-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-step>
|
||||
|
||||
<!-- ステップナビゲーション -->
|
||||
<template v-slot:navigation>
|
||||
<q-stepper-navigation>
|
||||
<div class="row justify-end q-mt-md">
|
||||
<q-btn v-if="step > 1" flat color="primary" @click="$refs.stepper.previous()" label="戻る" class="q-ml-sm" />
|
||||
<q-btn @click="stepperNext" color="primary" :label="step === 2 ? '確定' : '次へ'"
|
||||
:disable="nextBtnCheck()" />
|
||||
</div>
|
||||
</q-stepper-navigation>
|
||||
</template>
|
||||
</q-stepper>
|
||||
|
||||
<!-- フィールド選択ダイアログ -->
|
||||
<template v-for="(item, index) in data.fieldList" :key="`dg${item.id}`">
|
||||
<show-dialog v-model:visible="item.sourceDg.show" name="フィールド一覧" min-width="400px">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="item.sourceDg.filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<FieldSelect name="フィールド" :appId="data.sourceApp.id" :selectedFields="item.source"
|
||||
:filter="item.sourceDg.filter" :updateSelectFields="(f) => updateSelectField(f, item, index, 'source')"
|
||||
:blackListLabel="blackListLabel" />
|
||||
</show-dialog>
|
||||
|
||||
<show-dialog v-model:visible="item.dropDownDg.show" name="フィールド一覧" min-width="400px">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="item.dropDownDg.filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<FieldSelect name="フィールド" :appId="data.dropDownApp.id" :selectedFields="item.source"
|
||||
:filter="item.dropDownDg.filter" :updateSelectFields="(f) => updateSelectField(f, item, index, 'dropDown')"
|
||||
:blackListLabel="blackListLabel" />
|
||||
</show-dialog>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, watchEffect,watch } from 'vue';
|
||||
import ShowDialog from './ShowDialog.vue';
|
||||
import AppSelectBox from './AppSelectBox.vue';
|
||||
import FieldSelect from './FieldSelect.vue';
|
||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||
import { useFlowEditorStore } from 'src/stores/flowEditor';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CascadingDropDownBox',
|
||||
inheritAttrs: false,
|
||||
components: { ShowDialog, AppSelectBox, FieldSelect },
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
finishDialogHandler: Function,
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { emit }) {
|
||||
const authStore = useAuthStore();
|
||||
const flowStore = useFlowEditorStore();
|
||||
const $q = useQuasar();
|
||||
const appDg = ref();
|
||||
const stepper = ref();
|
||||
const step = ref(1);
|
||||
|
||||
const data =ref(props.modelValue);
|
||||
// const data = ref({
|
||||
// sourceApp: props.modelValue.sourceApp ?? { appFilter: '', showSelectApp: false },
|
||||
// dropDownApp: props.modelValue.dropDownApp,
|
||||
// fieldList: props.modelValue.fieldList ?? [],
|
||||
// });
|
||||
|
||||
// アプリ関連の関数
|
||||
const showAppDialog = () => data.value.sourceApp.showSelectApp = true;
|
||||
|
||||
const clearSelectedApp = () => {
|
||||
data.value.sourceApp = { appFilter: '', showSelectApp: false };
|
||||
data.value.fieldList = [];
|
||||
};
|
||||
|
||||
const closeAppDialog = (val: 'OK' | 'Cancel') => {
|
||||
data.value.sourceApp.showSelectApp = false;
|
||||
const selected = appDg.value?.selected[0];
|
||||
if (val === 'OK' && selected) {
|
||||
if (flowStore.appInfo?.appId === selected.id) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: "エラー",
|
||||
message: 'データソースを現在のアプリにすることはできません。'
|
||||
});
|
||||
} else if (selected.id !== data.value.sourceApp.id) {
|
||||
clearSelectedApp();
|
||||
Object.assign(data.value.sourceApp, { id: selected.id, name: selected.name });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// フィールド関連の関数
|
||||
const defaultRow = () => ({
|
||||
id: uuidv4(),
|
||||
source: undefined,
|
||||
dropDown: undefined,
|
||||
sourceDg: { show: false, filter: '' },
|
||||
dropDownDg: { show: false, filter: '' },
|
||||
});
|
||||
|
||||
const addRow = () => data.value.fieldList.push(defaultRow());
|
||||
const delRow = (index: number) => data.value.fieldList.splice(index, 1);
|
||||
|
||||
const showFieldDialog = (item: any, keyName: string) => item[`${keyName}Dg`].show = true;
|
||||
|
||||
const updateSelectField = (f: any, item: any, index: number, keyName: 'source' | 'dropDown') => {
|
||||
const [selected] = f.value;
|
||||
const isDuplicate = data.value.fieldList.some((field, idx) =>
|
||||
idx !== index && (field[keyName]?.code === selected.code || field[keyName]?.label === selected.label)
|
||||
);
|
||||
|
||||
if (isDuplicate) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: "エラー",
|
||||
message: '重複したフィールドは選択できません'
|
||||
});
|
||||
} else {
|
||||
item[keyName] = selected;
|
||||
}
|
||||
};
|
||||
|
||||
// ステッパー関連の関数
|
||||
const nextBtnCheck = () => {
|
||||
const stepNo = step.value
|
||||
if (stepNo === 1) {
|
||||
return !(data.value.sourceApp?.id && data.value.fieldList?.length > 0 && data.value.fieldList?.every(f => f.source?.name));
|
||||
} else if (stepNo === 2) {
|
||||
return !data.value.fieldList?.every(f => f.dropDown?.name);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const stepperNext = () => {
|
||||
if (step.value === 2) {
|
||||
props.finishDialogHandler?.(data.value);
|
||||
} else {
|
||||
data.value.dropDownApp = { name: flowStore.appInfo?.name, id: flowStore.appInfo?.appId };
|
||||
stepper.value?.next();
|
||||
}
|
||||
};
|
||||
|
||||
// // データ変更の監視
|
||||
// watchEffect(() =>{
|
||||
// emit('update:modelValue', data.value);
|
||||
// });
|
||||
|
||||
return {
|
||||
// 状態と参照
|
||||
authStore,
|
||||
step,
|
||||
stepper,
|
||||
appDg,
|
||||
data,
|
||||
// アプリ関連の関数
|
||||
showAppDialog,
|
||||
closeAppDialog,
|
||||
clearSelectedApp,
|
||||
|
||||
// フィールド関連の関数
|
||||
addRow,
|
||||
delRow,
|
||||
showFieldDialog,
|
||||
updateSelectField,
|
||||
|
||||
// ステッパー関連の関数
|
||||
nextBtnCheck,
|
||||
stepperNext,
|
||||
|
||||
// 定数
|
||||
blackListLabel: ['レコード番号', '作業者', '更新者', '更新日時', '作成日時', '作成者', 'カテゴリー', 'ステータス'],
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -52,12 +52,12 @@ import { useQuasar } from 'quasar';
|
||||
const tree = ref(props.conditionTree);
|
||||
const closeDg = (val:string) => {
|
||||
if (val == 'OK') {
|
||||
// if(tree.value.root.children.length===0){
|
||||
// $q.notify({
|
||||
// type: 'negative',
|
||||
// message: `条件式を設定してください。`
|
||||
// });
|
||||
// }
|
||||
if(tree.value.root.children.length===0){
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: `条件式を設定してください。`
|
||||
});
|
||||
}
|
||||
context.emit("update:conditionTree",tree.value);
|
||||
}
|
||||
showflg.value=false;
|
||||
|
||||
@@ -1,144 +1,96 @@
|
||||
<template>
|
||||
<q-field labelColor="primary" class="condition-object" dense outlined :label="label" :disable="disabled"
|
||||
:clearable="isSelected">
|
||||
<template v-slot:control>
|
||||
<q-chip color="primary" text-color="white" v-if="isSelected && selectedObject.objectType==='field'" :dense="true" class="selected-obj">
|
||||
{{ selectedObject.name }}
|
||||
</q-chip>
|
||||
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
|
||||
{{ selectedObject.name.name }}
|
||||
</q-chip>
|
||||
<div v-if="isSelected && selectedObject.objectType==='text'">{{ selectedObject?.sharedText }}</div>
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" class="cursor-pointer" @click="showDg" />
|
||||
</template>
|
||||
</q-field>
|
||||
<show-dialog v-model:visible="show" name="設定項目" @close="closeDg" min-width="400px">
|
||||
<!-- <template v-slot:toolbar>
|
||||
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
|
||||
-->
|
||||
<DynamicItemInput v-model:selectedObject="selectedObject" :canInput="config.canInput"
|
||||
:buttonsConfig="config.buttonsConfig" :appId="store.appInfo?.appId" :options="options" ref="inputRef" />
|
||||
|
||||
<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.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" min-width="400px">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
|
||||
</show-dialog>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, ref, watchEffect, computed ,PropType} from 'vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
// import ConditionObjects from '../ConditionObjects.vue';
|
||||
import DynamicItemInput from '../DynamicItemInput/DynamicItemInput.vue';
|
||||
import { useFlowEditorStore } from '../../stores/flowEditor';
|
||||
import { IActionFlow, IActionNode, IActionVariable } from '../../types/ActionTypes';
|
||||
import { IDynamicInputConfig } from 'src/types/ComponentTypes';
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ConditionObject',
|
||||
components: {
|
||||
ShowDialog,
|
||||
DynamicItemInput,
|
||||
// ConditionObjects
|
||||
},
|
||||
props: {
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
<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
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: undefined
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
},
|
||||
config: {
|
||||
type: Object as PropType<IDynamicInputConfig>,
|
||||
default: () => {
|
||||
return {
|
||||
canInput: false,
|
||||
buttonsConfig: [
|
||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
||||
]
|
||||
};
|
||||
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;
|
||||
};
|
||||
|
||||
options:
|
||||
{
|
||||
type:Array as PropType< string[]>,
|
||||
default:()=>[]
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
// const appDg = ref();
|
||||
const inputRef=ref();
|
||||
const show = ref(false);
|
||||
const selectedObject = ref(props.modelValue);
|
||||
const store = useFlowEditorStore();
|
||||
// const sharedText = ref(''); // 共享的文本状态
|
||||
const isSelected = computed(() => {
|
||||
return selectedObject.value?.sharedText !== '';
|
||||
});
|
||||
// const isSelected = computed(()=>{
|
||||
// return selectedObject.value!==null && typeof selectedObject.value === 'object' && ('name' in selectedObject.value)
|
||||
// });
|
||||
let vars: IActionVariable[] = [];
|
||||
if (store.currentFlow !== undefined && store.activeNode !== undefined) {
|
||||
vars = store.currentFlow.getVarNames(store.activeNode);
|
||||
const 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
|
||||
};
|
||||
}
|
||||
// const filter=ref('');
|
||||
const showDg = () => {
|
||||
show.value = true;
|
||||
};
|
||||
|
||||
const closeDg = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
// selectedObject.value = appDg.value.selected[0];
|
||||
selectedObject.value = inputRef.value.selectedObjectRef
|
||||
}
|
||||
};
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedObject.value);
|
||||
});
|
||||
|
||||
return {
|
||||
inputRef,
|
||||
store,
|
||||
// appDg,
|
||||
show,
|
||||
showDg,
|
||||
closeDg,
|
||||
selectedObject,
|
||||
vars: reactive(vars),
|
||||
isSelected,
|
||||
buttonsConfig: [
|
||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
||||
]
|
||||
// filter
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.condition-object {
|
||||
.condition-object{
|
||||
min-width: 200px;
|
||||
max-height: 40px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.selected-obj {
|
||||
.selected-obj{
|
||||
margin: 0 2px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -67,21 +67,17 @@
|
||||
<!-- condition -->
|
||||
<div @click.stop @keypress.stop v-else >
|
||||
<div class="row no-wrap items-center q-my-xs">
|
||||
<ConditionObject v-bind="prop.node" v-model="prop.node.object" :config="leftDynamicItemConfig" class="col-4"/>
|
||||
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
|
||||
<ConditionObject v-bind="prop.node" v-model="prop.node.value" :config="rightDynamicItemConfig" class="col-4"
|
||||
:options="objectValueOptions(prop.node?.object?.options)"
|
||||
/>
|
||||
<!-- <ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"/> -->
|
||||
<!-- <q-input v-if="!prop.node.object || !('options' in prop.node.object)"
|
||||
<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)"
|
||||
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> -->
|
||||
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>
|
||||
@@ -120,7 +116,6 @@ import { finished } from 'stream';
|
||||
import { defineComponent,ref,reactive, computed, inject } from 'vue';
|
||||
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
|
||||
import ConditionObject from './ConditionObject.vue';
|
||||
import { IDynamicInputConfig } from 'src/types/ComponentTypes';
|
||||
export default defineComponent( {
|
||||
name: 'NodeCondition',
|
||||
components: {
|
||||
@@ -148,18 +143,17 @@ export default defineComponent( {
|
||||
return opts;
|
||||
});
|
||||
|
||||
const operatorSet = inject<Array<any>>('Operator')
|
||||
const operators = ref(operatorSet ? operatorSet : Object.values(Operator));
|
||||
const operator = inject('Operator')
|
||||
const operators =computed(()=>{
|
||||
return operator ? operator : Object.values(Operator);
|
||||
});
|
||||
const tree = reactive(props.conditionTree);
|
||||
|
||||
const conditionString = computed(()=>{
|
||||
return tree.buildConditionString(tree.root);
|
||||
});
|
||||
|
||||
const objectValueOptions=(options:any):any[]|null=>{
|
||||
if(!options){
|
||||
return null;
|
||||
}
|
||||
const objectValueOptions=(options:any):any[]=>{
|
||||
const opts:any[] =[];
|
||||
Object.keys(options).forEach((key) =>
|
||||
{
|
||||
@@ -226,14 +220,11 @@ export default defineComponent( {
|
||||
ticked.value=[];
|
||||
}
|
||||
|
||||
|
||||
const expanded=computed(()=>tree.getGroups(tree.root));
|
||||
// addCondition(tree.root);
|
||||
const leftDynamicItemConfig = inject<IDynamicInputConfig>('leftDynamicItemConfig');
|
||||
const rightDynamicItemConfig = inject<IDynamicInputConfig>('rightDynamicItemConfig');
|
||||
|
||||
return {
|
||||
leftDynamicItemConfig,
|
||||
rightDynamicItemConfig,
|
||||
showingCondition,
|
||||
conditionString,
|
||||
tree,
|
||||
@@ -265,12 +256,10 @@ export default defineComponent( {
|
||||
max-height: 40px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.operator{
|
||||
min-width: 150px;
|
||||
max-height: 40px;
|
||||
margin: 0 2px;
|
||||
|
||||
text-align: center;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
:url="uploadUrl"
|
||||
:label="title"
|
||||
:headers="headers"
|
||||
accept=".xlsx"
|
||||
accept=".csv,.xlsx"
|
||||
v-on:rejected="onRejected"
|
||||
v-on:uploaded="onUploadFinished"
|
||||
v-on:failed="onFailed"
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
</template>
|
||||
<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();
|
||||
@@ -30,7 +30,7 @@ import { ref } from 'vue';
|
||||
// https://quasar.dev/quasar-plugins/notify#Installation
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
message: `Excelファイルを選択してください。`
|
||||
message: `CSVおよびExcelファイルを選択してください。`
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ import { ref } from 'vue';
|
||||
function onUploadFinished({xhr}:{xhr:XMLHttpRequest}){
|
||||
let msg="ファイルのアップロードが完了しました。";
|
||||
if(xhr && xhr.response){
|
||||
msg=`${msg} (${xhr.responseText})`;
|
||||
msg=`${msg} (${xhr.responseText})`;
|
||||
}
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
@@ -52,28 +52,14 @@ import { ref } from 'vue';
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 例外発生時、responseからエラー情報を取得する
|
||||
* @param xhr
|
||||
*/
|
||||
function getResponseError(xhr:XMLHttpRequest){
|
||||
try{
|
||||
const resp = JSON.parse(xhr.responseText);
|
||||
return 'detail' in resp ? resp.detail:'';
|
||||
}catch(err){
|
||||
return xhr.responseText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param info ファイルアップロード失敗時の処理
|
||||
*/
|
||||
function onFailed({files,xhr}:{files: readonly any[],xhr:XMLHttpRequest}){
|
||||
function onFailed({files,xhr}:{files: readonly any[],xhr:any}){
|
||||
let msg ="ファイルアップロードが失敗しました。";
|
||||
if(xhr && xhr.status){
|
||||
const detail = getResponseError(xhr);
|
||||
msg=`${msg} (${xhr.status }:${detail})`
|
||||
msg=`${msg} (${xhr.status }:${xhr.statusText})`
|
||||
}
|
||||
$q.notify({
|
||||
type:"negative",
|
||||
@@ -88,7 +74,7 @@ import { ref } from 'vue';
|
||||
|
||||
const headers = ref([{name:"Authorization",value:'Bearer ' + authStore.token}]);
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
title:"設計書から導入する(Excel)",
|
||||
title:"設計書から導入する(csv or excel)",
|
||||
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`
|
||||
|
||||
});
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
<template>
|
||||
<q-btn-dropdown
|
||||
class="customized-disabled-btn"
|
||||
color="primay"
|
||||
push
|
||||
flat
|
||||
no-caps
|
||||
icon="share"
|
||||
size="md"
|
||||
:label="userStore.currentDomain.domainName"
|
||||
:disable-dropdown="isUnclickable"
|
||||
:dropdown-icon="isUnclickable ? 'none' : ''"
|
||||
:disable="isUnclickable"
|
||||
>
|
||||
<q-list>
|
||||
<q-item v-for="domain in domains" :key="domain.domainName"
|
||||
@@ -29,32 +26,18 @@
|
||||
<script setup lang="ts" >
|
||||
import { IDomainInfo } from 'src/types/ActionTypes';
|
||||
import { useAuthStore,IUserState } from 'stores/useAuthStore';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ref } from 'vue';
|
||||
const userStore = useAuthStore();
|
||||
const route = useRoute()
|
||||
const domains = ref<IDomainInfo[]>([]);
|
||||
(async ()=>{
|
||||
domains.value = await userStore.getUserDomains();
|
||||
})();
|
||||
|
||||
const isUnclickable = computed(()=>{
|
||||
return route.path.startsWith('/FlowChart/') || domains.value === undefined || domains.value.length === 0;
|
||||
});
|
||||
|
||||
|
||||
const onItemClick=(domain:IDomainInfo)=>{
|
||||
console.log(domain);
|
||||
userStore.setCurrentDomain(domain);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.q-btn.disabled.customized-disabled-btn {
|
||||
opacity: 1 !important;
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
.q-btn.disabled.customized-disabled-btn * {
|
||||
cursor: default !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
<template>
|
||||
<div class="q-mx-md" style="max-width: 600px;">
|
||||
<!-- <q-card> -->
|
||||
<div class="q-mb-md">
|
||||
<q-input ref="inputRef" v-if="!optionsRef|| optionsRef.length===0"
|
||||
outlined dense debounce="200" @update:model-value="updateSharedText"
|
||||
v-model="sharedText" :readonly="!canInputFlag" autogrow>
|
||||
<template v-slot:append>
|
||||
<q-btn flat round padding="none" icon="cancel" @click="clearSharedText" color="grey-6" />
|
||||
</template>
|
||||
</q-input>
|
||||
<q-select v-if="optionsRef && optionsRef.length>0"
|
||||
:model-value="sharedText"
|
||||
:options="optionsRef"
|
||||
clearable
|
||||
value-key="index"
|
||||
outlined
|
||||
dense
|
||||
use-input
|
||||
hide-selected
|
||||
input-debounce="10"
|
||||
fill-input
|
||||
@input-value="setValue"
|
||||
@clear="sharedText=null"
|
||||
hide-dropdown-icon
|
||||
:readonly="!canInputFlag"
|
||||
>
|
||||
</q-select>
|
||||
</div>
|
||||
|
||||
<div class="row q-gutter-sm">
|
||||
<q-btn v-for="button in buttonsConfig" :key="button.type" :color="button.color" @mousedown.prevent
|
||||
@click="openDialog(button)" size="sm">
|
||||
{{ button.label }}
|
||||
</q-btn>
|
||||
</div>
|
||||
|
||||
<show-dialog v-model:visible="dialogVisible" :name="currentDialogName" @close="closeDialog" min-width="400px">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="200" v-model="filter" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<!-- asdf -->
|
||||
<component :is="currentComponent" @select="handleSelect" :filter="filter" :appId="appId" />
|
||||
</show-dialog>
|
||||
<!-- </q-card> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ref, inject, watchEffect, defineComponent,PropType } from 'vue';
|
||||
import FieldAdd from './FieldAdd.vue';
|
||||
import VariableAdd from './VariableAdd.vue';
|
||||
// import FunctionAdd from './FunctionAdd.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import { IButtonConfig } from 'src/types/ComponentTypes';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DynamicItemInput',
|
||||
components: {
|
||||
FieldAdd,
|
||||
VariableAdd,
|
||||
// FunctionAdd,
|
||||
ShowDialog
|
||||
},
|
||||
props: {
|
||||
canInput: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
appId: {
|
||||
type: String,
|
||||
},
|
||||
selectedObject: {
|
||||
default: {}
|
||||
},
|
||||
options:{
|
||||
type:Array as PropType< string[]>
|
||||
},
|
||||
buttonsConfig: {
|
||||
type: Array as PropType<IButtonConfig[]>,
|
||||
default: () => [
|
||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' }
|
||||
]
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const filter = ref('');
|
||||
const dialogVisible = ref(false);
|
||||
const currentDialogName = ref('');
|
||||
const selectedObjectRef = ref(props.selectedObject);
|
||||
const currentComponent = ref('FieldAdd');
|
||||
const sharedText = ref(props.selectedObject?.sharedText ?? '');
|
||||
const inputRef = ref();
|
||||
const canInputFlag = ref(props.canInput);
|
||||
const editable = ref(false);
|
||||
|
||||
const openDialog = (button: IButtonConfig) => {
|
||||
currentDialogName.value = button.label;
|
||||
currentComponent.value = button.type;
|
||||
dialogVisible.value = true;
|
||||
editable.value = canInputFlag.value;
|
||||
};
|
||||
|
||||
const closeDialog = () => {
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
const handleSelect = (value:any) => {
|
||||
|
||||
if (value && value._t && (value._t as string).length > 0) {
|
||||
canInputFlag.value = editable.value;
|
||||
}
|
||||
selectedObjectRef.value={ sharedText: value._t, ...value };
|
||||
sharedText.value = `${value._t}`;
|
||||
// emit('update:selectedObject', { sharedText: sharedText.value, ...value });
|
||||
dialogVisible.value = false;
|
||||
};
|
||||
|
||||
const clearSharedText = () => {
|
||||
sharedText.value = '';
|
||||
selectedObjectRef.value={};
|
||||
canInputFlag.value = true;
|
||||
// emit('update:selectedObject', {});
|
||||
}
|
||||
const updateSharedText = (value:string) => {
|
||||
sharedText.value = value;
|
||||
selectedObjectRef.value= { sharedText: value,objectType:'text' }
|
||||
// emit('update:selectedObject', { ...props.selectedObject, sharedText: value,objectType:'text' });
|
||||
}
|
||||
const setValue=(value:string)=>{
|
||||
sharedText.value = value;
|
||||
if(selectedObjectRef.value.sharedText!==value){
|
||||
selectedObjectRef.value= { sharedText: value,objectType:'text' }
|
||||
}
|
||||
}
|
||||
const optionsRef=ref(props.options);
|
||||
|
||||
return {
|
||||
filter,
|
||||
dialogVisible,
|
||||
currentDialogName,
|
||||
currentComponent,
|
||||
canInputFlag,
|
||||
openDialog,
|
||||
closeDialog,
|
||||
handleSelect,
|
||||
clearSharedText,
|
||||
updateSharedText,
|
||||
setValue,
|
||||
sharedText,
|
||||
inputRef,
|
||||
optionsRef,
|
||||
selectedObjectRef
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,41 +0,0 @@
|
||||
<template>
|
||||
<field-list v-model="selected" type="single" :filter="filter" :appId="sourceApp ? sourceApp : appId"
|
||||
:fields="sourceFields" @update:modelValue="handleSelect" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { computed, inject, ref } from 'vue';
|
||||
import FieldList from '../FieldList.vue';
|
||||
export default {
|
||||
name: 'FieldAdd',
|
||||
components: {
|
||||
FieldList,
|
||||
},
|
||||
props: {
|
||||
appId: Number,
|
||||
filter: String
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const sourceFields = inject<Array<unknown>>('sourceFields')
|
||||
const sourceApp = inject<number>('sourceApp')
|
||||
const appId = computed(() => {
|
||||
if (sourceFields || sourceApp) {
|
||||
return sourceApp.value
|
||||
} else {
|
||||
return props.appId
|
||||
}
|
||||
});
|
||||
return {
|
||||
sourceFields,
|
||||
sourceApp,
|
||||
selected: ref([]),
|
||||
handleSelect: (newSelection: any[]) => {
|
||||
|
||||
if (newSelection.length > 0) {
|
||||
const v = newSelection[0]
|
||||
emit('select', { _t: `field(${appId.value},${v.name})`, ...v }); // 假设您只需要选择的第一个字段的名称
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<variable-list v-model="selected" type="single" :vars="vars" :filter="filter" @update:modelValue="handleSelect" />
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import VariableList from '../VariableList.vue';
|
||||
import { useFlowEditorStore } from 'src/stores/flowEditor';
|
||||
import { IActionVariable } from 'src/types/ActionTypes';
|
||||
export default {
|
||||
name: 'VariableAdd',
|
||||
components: {
|
||||
VariableList,
|
||||
},
|
||||
props: {
|
||||
appId: Number,
|
||||
filter: String
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const store = useFlowEditorStore();
|
||||
let vars: IActionVariable[] = [];
|
||||
console.log(store.currentFlow !== undefined && store.activeNode !== undefined);
|
||||
|
||||
if (store.currentFlow !== undefined && store.activeNode !== undefined) {
|
||||
vars = store.currentFlow.getVarNames(store.activeNode);
|
||||
}
|
||||
return {
|
||||
vars,
|
||||
selected: ref([]),
|
||||
handleSelect: (newSelection: any[]) => {
|
||||
if (newSelection.length > 0) {
|
||||
const v = newSelection[0];
|
||||
let name = v.name
|
||||
if (typeof name === 'object') {
|
||||
name = name.name
|
||||
}
|
||||
emit('select', { _t: `var(${name})`, ...v }); // 假设您只需要选择的第一个字段的名称
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -19,7 +19,6 @@
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-separator
|
||||
class="q-my-sm"
|
||||
v-if="isSeparator"
|
||||
inset
|
||||
/>
|
||||
|
||||
@@ -1,24 +1,18 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" :selected="modelValue"
|
||||
@update:selected="$emit('update:modelValue', $event)"
|
||||
:filter="filter"
|
||||
:columns="columns"
|
||||
:rows="rows"
|
||||
:pagination="pagination"
|
||||
style="max-height: 55vh;"/>
|
||||
@update:selected="$emit('update:modelValue', $event)" :filter="filter" :columns="columns" :rows="rows" />
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { useAsyncState } from '@vueuse/core';
|
||||
import { api } from 'boot/axios';
|
||||
import { computed ,Prop,PropType,ref} from 'vue';
|
||||
import {IField} from 'src/types/ComponentTypes';
|
||||
import { computed } from 'vue';
|
||||
|
||||
export default {
|
||||
name: 'FieldList',
|
||||
props: {
|
||||
fields: Array as PropType<IField[]>,
|
||||
fields: Array,
|
||||
name: String,
|
||||
type: String,
|
||||
appId: Number,
|
||||
@@ -38,30 +32,25 @@ export default {
|
||||
]
|
||||
|
||||
const { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
|
||||
if (props.fields && Object.keys(props.fields).length > 0) {
|
||||
return props.fields.map(f => ({ name: f.label, ...f ,objectType: 'field'}));
|
||||
if (props.fields) {
|
||||
return props.fields.map(f => ({ name: f.label, objectType: 'field', ...f }));
|
||||
} else {
|
||||
return api.get('api/v1/appfields', {
|
||||
params: {
|
||||
app: props.appId
|
||||
}
|
||||
}).then(res => {
|
||||
const fields = res.data.properties;
|
||||
return Object.values(fields).map((f:any) => ({ name: f.label, objectType: 'field', ...f }));
|
||||
console.log(res);
|
||||
return Object.values(res.data.properties).map(f => ({ name: f.label, objectType: 'field', ...f }));
|
||||
});
|
||||
}
|
||||
}, [{ name: '', objectType: '', type: '', code: '', label: '' }])
|
||||
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
// selected: ref([]),
|
||||
isLoaded,
|
||||
pagination: ref({
|
||||
rowsPerPage: 25,
|
||||
sortBy: 'name',
|
||||
descending: false,
|
||||
page: 1,
|
||||
})
|
||||
isLoaded
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||
<q-spinner color="primary" size="3em" />
|
||||
</div>
|
||||
<q-table flat bordered v-else row-key="id" :selection="type" v-model:selected="selected" :columns="columns"
|
||||
<q-table flat bordered v-else row-key="name" :selection="type" v-model:selected="selected" :columns="columns"
|
||||
:rows="rows" :pagination="pageSetting" :filter="filter" style="max-height: 55vh;"/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -11,7 +11,6 @@
|
||||
import { ref, onMounted, reactive, watchEffect } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
|
||||
export default {
|
||||
name: 'fieldSelect',
|
||||
props: {
|
||||
@@ -26,21 +25,13 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
selectedFields:{
|
||||
type:Array ,
|
||||
default:()=>[]
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
filter: String,
|
||||
updateSelectFields: {
|
||||
updateSelects: {
|
||||
type: Function
|
||||
},
|
||||
blackListLabel: {
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
}
|
||||
filter: String,
|
||||
},
|
||||
setup(props) {
|
||||
const isLoaded = ref(false);
|
||||
@@ -48,52 +39,37 @@ export default {
|
||||
{ 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 pageSetting = ref({
|
||||
sortBy: 'name',
|
||||
sortBy: 'desc',
|
||||
descending: false,
|
||||
page: 1,
|
||||
rowsPerPage: props.not_page ? 0 : 25
|
||||
page: 2,
|
||||
rowsPerPage: props.not_page ? 0 : 5
|
||||
// rowsNumber: xx if getting data from a server
|
||||
});
|
||||
const rows = reactive([]);
|
||||
const selected = ref((props.selectedFields && props.selectedFields.length>0)?props.selectedFields:[]);
|
||||
const selected = ref(props.selectedFields && props.selectedFields.length>0?props.selectedFields:[]);
|
||||
|
||||
watchEffect(() => {
|
||||
props.updateSelects(selected);
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const url = props.fieldTypes.includes('SPACER')?'api/v1/allfields':'api/v1/appfields';
|
||||
const res = await api.get(url, {
|
||||
const res = await api.get('api/v1/appfields', {
|
||||
params: {
|
||||
app: props.appId
|
||||
}
|
||||
});
|
||||
let fields = Object.values(res.data.properties);
|
||||
for (const index in fields) {
|
||||
const fld = fields[index]
|
||||
if(props.blackListLabel.length > 0){
|
||||
if(!props.blackListLabel.find(blackListItem => blackListItem === fld.label)){
|
||||
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
|
||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
||||
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
|
||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
|
||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
||||
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
|
||||
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
});
|
||||
|
||||
watchEffect(()=>{
|
||||
if (selected.value && selected.value[0] && props.updateSelectFields) {
|
||||
props.updateSelectFields(selected)
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
columns,
|
||||
rows,
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
<template>
|
||||
<!-- <div class="q-pa-md q-gutter-sm" > -->
|
||||
<q-dialog :model-value="visible" persistent bordered >
|
||||
<q-card class="" style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
|
||||
<q-card style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :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 class="q-mt-md" :style="sectionStyle">
|
||||
<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 v-if="!disableBtn" align="right" class="text-primary">
|
||||
<q-btn flat label="確定" :loading="okBtnLoading" :v-close-popup="okBtnAutoClose" @click="CloseDialogue('OK')" />
|
||||
<q-card-actions align="right" class="text-primary q-mt-lg">
|
||||
<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>
|
||||
@@ -29,16 +32,7 @@ export default {
|
||||
width:String,
|
||||
height:String,
|
||||
minWidth:String,
|
||||
minHeight:String,
|
||||
okBtnLoading:Boolean,
|
||||
okBtnAutoClose:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disableBtn:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
minHeight:String
|
||||
},
|
||||
emits: [
|
||||
'close'
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<q-table :rows="rows" :columns="columns" row-key="id" :filter="props.filter" :loading="loading"
|
||||
:pagination="pagination" selection="single" v-model:selected="selected"></q-table>
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
const props = defineProps<{filter:string}>()
|
||||
const columns = [
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||
{ name: 'firstName', label: '氏名', field: 'firstName', align: 'left', sortable: true },
|
||||
{ name: 'lastName', label: '苗字', field: 'lastName', align: 'left', sortable: true },
|
||||
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||
];
|
||||
|
||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 10 });
|
||||
const rows = ref([]);
|
||||
const loading = ref(false);
|
||||
const selected = ref([]);
|
||||
defineExpose({
|
||||
selected
|
||||
})
|
||||
const getUsers = async (filter = () => true) => {
|
||||
loading.value = true;
|
||||
const result = await api.get(`api/v1/users`);
|
||||
rows.value = result.data.map((item) => {
|
||||
return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active }
|
||||
}).filter(filter);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getUsers();
|
||||
})
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<q-table flat bordered row-key="id" :selection="type" :selected="modelValue" :filter="filter"
|
||||
<q-table flat bordered row-key="id" :selection="type" :selected="modelValue"
|
||||
@update:selected="$emit('update:modelValue', $event)" :columns="columns" :rows="rows" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -19,8 +19,7 @@ export default {
|
||||
reqired: true,
|
||||
default: () => []
|
||||
},
|
||||
modelValue: Array,
|
||||
filter: String
|
||||
modelValue: Array
|
||||
},
|
||||
emits: [
|
||||
'update:modelValue'
|
||||
|
||||
@@ -3,34 +3,42 @@
|
||||
<q-tree :nodes="store.eventTree.screens" node-key="eventId" children-key="events" no-connectors
|
||||
v-model:expanded="store.expandedScreen" :dense="true" :ref="tree">
|
||||
<template v-slot:header-EVENT="prop">
|
||||
<div :ref="prop.node.eventId" class="row col items-center no-wrap event-node" @click="onSelected(prop.node)">
|
||||
<div :ref="prop.node.eventId" class="row col items-center no-wrap event-node">
|
||||
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
|
||||
class="q-mr-sm">
|
||||
</q-icon>
|
||||
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div>
|
||||
<div class="no-wrap" @click="onSelected(prop.node)"
|
||||
: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" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div>
|
||||
<div class="row col items-center 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>
|
||||
<template v-slot:header-DELETABLE="prop">
|
||||
<div class="row col items-start event-node" @click="onSelected(prop.node)">
|
||||
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px" class="q-mr-sm" />
|
||||
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div>
|
||||
<q-space></q-space>
|
||||
<q-icon name="delete_forever" color="negative" size="16px" @click="deleteEvent(prop.node)"></q-icon>
|
||||
<div class="row col items-center event-node">
|
||||
<div class="row col items-center" @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>{{ prop.node.label }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<q-btn class="q-mr-sm delete-btn" flat fab-mini icon="delete_forever" padding="none" color="negative"
|
||||
@click="deleteEvent(prop.node)"></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</q-tree>
|
||||
<show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg">
|
||||
<field-select ref="appDg" name="フィールド" type="single" :fieldTypes="fieldTypes" :appId="store.appInfo?.appId"></field-select>
|
||||
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
|
||||
</show-dialog>
|
||||
</template>
|
||||
|
||||
@@ -38,8 +46,8 @@
|
||||
import { QTree, useQuasar } from 'quasar';
|
||||
import { ActionFlow, RootAction } from 'src/types/ActionTypes';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import { defineComponent, ref, watchEffect } from 'vue';
|
||||
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode, kintoneEvent } from '../../types/KintoneEvents';
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode } from '../../types/KintoneEvents';
|
||||
import FieldSelect from '../FieldSelect.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
export default defineComponent({
|
||||
@@ -54,33 +62,15 @@ export default defineComponent({
|
||||
const store = useFlowEditorStore();
|
||||
const showDialog = ref(false);
|
||||
const tree = ref<QTree>();
|
||||
const fieldTypes=[
|
||||
'RADIO_BUTTON',
|
||||
'DROP_DOWN',
|
||||
'CHECK_BOX',
|
||||
'MULTI_SELECT',
|
||||
'USER_SELECT',
|
||||
'GROUP_SELECT',
|
||||
'ORGANIZATION_SELECT',
|
||||
'DATE',
|
||||
'DATETIME',
|
||||
'TIME',
|
||||
'SINGLE_LINE_TEXT',
|
||||
'NUMBER'];
|
||||
// const eventTree=ref(kintoneEvents);
|
||||
// const selectedFlow = store.currentFlow;
|
||||
|
||||
// const expanded=ref();
|
||||
const selectedEvent = ref<IKintoneEvent | undefined>(store.selectedEvent);
|
||||
const selectedChangeEvent = ref<IKintoneEventGroup | undefined>(undefined);
|
||||
const 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 getSelectedClass = (node: IKintoneEventNode) => {
|
||||
return store.selectedEvent && node.eventId === store.selectedEvent.eventId ? 'selected-node' : '';
|
||||
};
|
||||
|
||||
//フィールド値変更イベント追加
|
||||
const closeDg = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
@@ -90,12 +80,12 @@ export default defineComponent({
|
||||
if (store.eventTree.findEventById(eventid)) {
|
||||
return;
|
||||
}
|
||||
selectedChangeEvent.value?.events.push(new kintoneEvent(
|
||||
field.name,
|
||||
eventid,
|
||||
selectedChangeEvent.value.eventId,
|
||||
'DELETABLE'
|
||||
));
|
||||
selectedChangeEvent.value?.events.push({
|
||||
eventId: eventid,
|
||||
label: field.name,
|
||||
parentId: selectedChangeEvent.value.eventId,
|
||||
header: 'DELETABLE'
|
||||
});
|
||||
tree.value?.expanded?.push(selectedChangeEvent.value.eventId);
|
||||
tree.value?.expandAll();
|
||||
}
|
||||
@@ -133,7 +123,7 @@ export default defineComponent({
|
||||
const screen = store.eventTree.findEventById(node.parentId);
|
||||
|
||||
let flow = store.findFlowByEventId(node.eventId);
|
||||
let screenName = screen !== null ? screen.label : '';
|
||||
let screenName = screen !== null ? screen.label : "";
|
||||
let nodeLabel = node.label;
|
||||
// if(isFieldChange(node)){
|
||||
// screenName=nodeLabel;
|
||||
@@ -150,9 +140,6 @@ export default defineComponent({
|
||||
selectedEvent.value.flowData = flow;
|
||||
}
|
||||
};
|
||||
watchEffect(()=>{
|
||||
store.setCurrentEvent(selectedEvent.value);
|
||||
});
|
||||
return {
|
||||
// eventTree,
|
||||
// expanded,
|
||||
@@ -160,14 +147,12 @@ export default defineComponent({
|
||||
tree,
|
||||
showDialog,
|
||||
isFieldChange,
|
||||
getSelectedClass,
|
||||
onSelected,
|
||||
selectedEvent,
|
||||
addChangeEvent,
|
||||
deleteEvent,
|
||||
closeDg,
|
||||
store,
|
||||
fieldTypes
|
||||
store
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,165 +1,140 @@
|
||||
<template>
|
||||
<div class="q-my-md" v-bind="$attrs">
|
||||
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
|
||||
:rules="rulesExp" lazy-rules="ondemand" @clear="clear" ref="fieldRef">
|
||||
<template v-slot:control>
|
||||
{{ isSelected ? selectedField.app?.name : "(未選択)" }}
|
||||
</template>
|
||||
<template v-slot:hint v-if="!isSelected">
|
||||
{{ placeholder }}
|
||||
</template>
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
||||
</template>
|
||||
</q-field>
|
||||
<div v-if="selectedField.fields && selectedField.fields.length > 0">
|
||||
<q-list bordered>
|
||||
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator v-slot="{ item, index }">
|
||||
<q-item :key="index" dense clickable>
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
{{ item.label }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-virtual-scroll>
|
||||
</q-list>
|
||||
<div class="q-my-md" v-bind="$attrs">
|
||||
<q-card flat>
|
||||
<q-card-section class="q-pa-none q-my-sm q-mr-md">
|
||||
<!-- <div class=" q-my-none ">App Field Select</div> -->
|
||||
<div class="row q-mb-xs">
|
||||
<div class="text-primary q-mb-xs text-caption">{{ $props.displayName }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="q-mb-xs">{{ selectedField.app?.name || '未選択' }}</div>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<q-btn round flat size="sm" color="primary" icon="search" @click="showDg" />
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-separator />
|
||||
<q-card-section class="q-pa-none q-ma-none">
|
||||
<div style="">
|
||||
<div v-if="selectedField.fields && selectedField.fields.length > 0">
|
||||
<q-list bordered>
|
||||
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator
|
||||
v-slot="{ item, index }">
|
||||
<q-item :key="index" dense clickable>
|
||||
<q-item-section>
|
||||
<q-item-label>
|
||||
{{ item.label }}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-virtual-scroll>
|
||||
</q-list>
|
||||
</div>
|
||||
<!-- <div v-else class="row q-mt-lg">
|
||||
</div> -->
|
||||
</div>
|
||||
<!-- <q-separator /> -->
|
||||
</q-card-section>
|
||||
<q-card-section class="q-px-none q-py-xs" v-if="selectedField.fields && selectedField.fields.length === 0">
|
||||
<div class="row">
|
||||
<div class="text-grey text-caption"> {{ $props.placeholder }}</div>
|
||||
<!-- <q-btn flat color="grey" label="clear" @click="clear" /> -->
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeAFBox">
|
||||
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" ref="afBox"
|
||||
:fieldTypes="fieldTypes" />
|
||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeFieldDialog" ref="fieldDlg">
|
||||
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" />
|
||||
</show-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, ref, watchEffect } from 'vue';
|
||||
import { defineComponent, ref, watchEffect } from 'vue';
|
||||
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
|
||||
export interface IApp {
|
||||
id: string,
|
||||
name: string
|
||||
id: string,
|
||||
name: string
|
||||
}
|
||||
export interface IField {
|
||||
name: string,
|
||||
code: string,
|
||||
type: string,
|
||||
label?: string
|
||||
name: string,
|
||||
code: string,
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface IAppFields {
|
||||
app?: IApp,
|
||||
fields: IField[]
|
||||
app?: IApp,
|
||||
fields: IField[]
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
name: 'AppFieldSelect2',
|
||||
components: {
|
||||
ShowDialog,
|
||||
AppFieldSelectBox
|
||||
},
|
||||
props: {
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
inheritAttrs: false,
|
||||
name: 'AppFieldSelect',
|
||||
components: {
|
||||
ShowDialog,
|
||||
AppFieldSelectBox
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
props: {
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
selectType: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
}
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
selectType: {
|
||||
type: String,
|
||||
default: 'single'
|
||||
},
|
||||
fieldTypes: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
setup(props, { emit }) {
|
||||
const show = ref(false);
|
||||
const selectedField = ref<IAppFields>({
|
||||
app: undefined,
|
||||
fields: []
|
||||
});
|
||||
if (props.modelValue && "app" in props.modelValue && "fields" in props.modelValue) {
|
||||
selectedField.value = props.modelValue as IAppFields;
|
||||
}
|
||||
const store = useFlowEditorStore();
|
||||
|
||||
const clear = () => {
|
||||
selectedField.value = {
|
||||
fields: []
|
||||
};
|
||||
}
|
||||
|
||||
const removeField = (index: number) => {
|
||||
selectedField.value.fields.splice(index, 1);
|
||||
}
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedField.value);
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
show,
|
||||
showDg: () => { show.value = true },
|
||||
selectedField,
|
||||
clear,
|
||||
removeField,
|
||||
};
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const show = ref(false);
|
||||
const afBox = ref();
|
||||
const fieldRef = ref();
|
||||
const selectedField = ref<IAppFields>({
|
||||
app: undefined,
|
||||
fields: []
|
||||
});
|
||||
if (props.modelValue && 'app' in props.modelValue && 'fields' in props.modelValue) {
|
||||
selectedField.value = props.modelValue as IAppFields;
|
||||
}
|
||||
const store = useFlowEditorStore();
|
||||
|
||||
const clear = () => {
|
||||
selectedField.value = {
|
||||
fields: []
|
||||
};
|
||||
}
|
||||
|
||||
const removeField = (index: number) => {
|
||||
selectedField.value.fields.splice(index, 1);
|
||||
}
|
||||
|
||||
const closeAFBox = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
console.log(afBox.value);
|
||||
selectedField.value = afBox.value.selField;
|
||||
fieldRef.value.validate();
|
||||
}
|
||||
};
|
||||
|
||||
const isSelected = computed(() => {
|
||||
return !!selectedField.value.app
|
||||
});
|
||||
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||
const requiredExp = props.required ? [((val: any) => (val && val.app && val.fields && val.fields.length > 0) || errmsg)] : [];
|
||||
const rulesExp = [...requiredExp, ...customExp];
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedField.value);
|
||||
});
|
||||
|
||||
return {
|
||||
store,
|
||||
afBox,
|
||||
show,
|
||||
showDg: () => { show.value = true },
|
||||
selectedField,
|
||||
clear,
|
||||
removeField,
|
||||
closeAFBox,
|
||||
isSelected,
|
||||
rulesExp,
|
||||
fieldRef
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-field :label="displayName" labelColor="primary" stack-label
|
||||
:rules="rulesExp"
|
||||
lazy-rules="ondemand"
|
||||
v-model="selectedApp"
|
||||
ref="fieldRef">
|
||||
<div>
|
||||
<q-field :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="() => { dgIsShow = true }">アプリ選択</q-btn>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-caption">
|
||||
<div v-if="selectedApp.app.name">
|
||||
{{ selectedApp.app.name }}
|
||||
<div v-if="selectedField.app.name">
|
||||
{{ selectedField.app.name }}
|
||||
</div>
|
||||
<div v-else>{{ placeholder }}</div>
|
||||
</q-card-section>
|
||||
@@ -47,6 +43,10 @@ export default defineComponent({
|
||||
AppSelectBox
|
||||
},
|
||||
props: {
|
||||
context: {
|
||||
type: Array<Props>,
|
||||
default: '',
|
||||
},
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
@@ -62,50 +62,31 @@ export default defineComponent({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const appDg = ref();
|
||||
const fieldRef=ref();
|
||||
const appDg = ref()
|
||||
const dgIsShow = ref(false)
|
||||
const selectedApp = props.modelValue && props.modelValue.app ? props.modelValue : reactive({app:{}});
|
||||
const selectedField = props.modelValue && props.modelValue.app ? props.modelValue : reactive({app:{}});
|
||||
const closeDg = (state: string) => {
|
||||
dgIsShow.value = false;
|
||||
if (state == 'OK') {
|
||||
selectedApp.app = appDg.value.selected[0];
|
||||
fieldRef.value.validate();
|
||||
selectedField.app = appDg.value.selected[0];
|
||||
}
|
||||
};
|
||||
//ルール設定
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
|
||||
const requiredExp = props.required ? [((val: any) => (!!val && !!val.app && !!val.app.name) || errmsg)] : [];
|
||||
const rulesExp = [...requiredExp, ...customExp];
|
||||
|
||||
console.log(selectedField);
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedApp);
|
||||
emit('update:modelValue', selectedField);
|
||||
});
|
||||
|
||||
return {
|
||||
filter: ref(''),
|
||||
dgIsShow,
|
||||
appDg,
|
||||
fieldRef,
|
||||
closeDg,
|
||||
selectedApp,
|
||||
rulesExp
|
||||
selectedField
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-field :label="displayName" labelColor="primary" stack-label lazy-rules="ondemand" ref="fieldRef">
|
||||
<template v-slot:control>
|
||||
<q-card flat class="full-width">
|
||||
<q-card-actions vertical>
|
||||
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">クリックで設定</q-btn>
|
||||
</q-card-actions>
|
||||
<q-card-section class="text-caption">
|
||||
<div v-if="data.dropDownApp?.name">
|
||||
{{ `${data.sourceApp?.name} -> ${data.dropDownApp?.name}` }}
|
||||
</div>
|
||||
<div v-else>{{ placeholder }}</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</template>
|
||||
</q-field>
|
||||
</div>
|
||||
|
||||
<ShowDialog v-model:visible="dgIsShow" name="ドロップダウン階層化設定" @close="closeDg" min-width="50vw" min-height="20vh" disableBtn>
|
||||
<template v-slot:toolbar>
|
||||
<q-btn flat round dense icon="more_vert" >
|
||||
<q-menu auto-close anchor="bottom start">
|
||||
<q-list>
|
||||
<q-item clickable @click="copySetting()">
|
||||
<q-item-section avatar><q-icon name="content_copy" ></q-icon></q-item-section>
|
||||
<q-item-section >コピー</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable @click="pasteSetting()">
|
||||
<q-item-section avatar><q-icon name="content_paste" ></q-icon></q-item-section>
|
||||
<q-item-section >貼り付け</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-menu>
|
||||
</q-btn>
|
||||
</template>
|
||||
<div class="q-mb-md q-ml-md q-mr-md">
|
||||
<CascadingDropDownBox v-model:model-value="data" :finishDialogHandler="finishDialogHandler" />
|
||||
</div>
|
||||
</ShowDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watchEffect } from 'vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import CascadingDropDownBox from '../CascadingDropDownBox.vue';
|
||||
|
||||
export default defineComponent({
|
||||
inheritAttrs: false,
|
||||
name: 'CascadingDropDown',
|
||||
components: {
|
||||
ShowDialog,
|
||||
CascadingDropDownBox
|
||||
},
|
||||
props: {
|
||||
displayName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => { ({}) }
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const dgIsShow = ref(false);
|
||||
// const data = ref(props.modelValue);
|
||||
const data = ref({
|
||||
sourceApp: props.modelValue.sourceApp ?? { appFilter: '', showSelectApp: false },
|
||||
dropDownApp: props.modelValue.dropDownApp,
|
||||
fieldList: props.modelValue.fieldList ?? [],
|
||||
});
|
||||
const closeDg = (state: string) => {
|
||||
dgIsShow.value = false;
|
||||
};
|
||||
|
||||
const finishDialogHandler = (boxData) => {
|
||||
data.value = boxData
|
||||
dgIsShow.value = false
|
||||
emit('update:modelValue', data.value);
|
||||
}
|
||||
|
||||
//設定をコピーする
|
||||
const copySetting=()=>{
|
||||
if (navigator.clipboard) {
|
||||
const jsonData= JSON.stringify(data.value);
|
||||
navigator.clipboard.writeText(jsonData).then(() => {
|
||||
console.log('Text successfully copied to clipboard');
|
||||
},
|
||||
(err) => {
|
||||
console.error('Error in copying text: ', err);
|
||||
});
|
||||
} else {
|
||||
console.log('Clipboard API not available');
|
||||
}
|
||||
};
|
||||
//設定を貼り付ける
|
||||
const pasteSetting=async ()=>{
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
console.log('Text from clipboard:', text);
|
||||
const jsonData=JSON.parse(text);
|
||||
if('sourceApp' in jsonData && 'dropDownApp' in jsonData && 'fieldList' in jsonData){
|
||||
const {sourceApp,dropDownApp, fieldList}=jsonData;
|
||||
data.value.sourceApp=sourceApp;
|
||||
data.value.dropDownApp=dropDownApp;
|
||||
data.value.fieldList=fieldList;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to read text from clipboard: ', err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// watchEffect(() => {
|
||||
// emit('update:modelValue', data.value);
|
||||
// });
|
||||
|
||||
return {
|
||||
dgIsShow,
|
||||
closeDg,
|
||||
data,
|
||||
finishDialogHandler,
|
||||
copySetting,
|
||||
pasteSetting
|
||||
};
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="" v-bind="$attrs">
|
||||
<q-field v-model="color" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label :bottom-slots="!isSelected" :rules="rulesExp">
|
||||
<q-field v-model="color" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label :bottom-slots="!isSelected" >
|
||||
<template v-slot:control>
|
||||
<q-chip text-color="black" color="white" v-if="isSelected">
|
||||
<div class="row">
|
||||
@@ -57,34 +57,17 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const color = ref(props.modelValue??"");
|
||||
const isSelected = computed(()=>props.modelValue && props.modelValue!=="");
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg ),"anyColor"]:[];
|
||||
const rulesExp=[...requiredExp,...customExp];
|
||||
watchEffect(()=>{
|
||||
emit('update:modelValue', color.value);
|
||||
});
|
||||
return {
|
||||
color,
|
||||
isSelected,
|
||||
rulesExp
|
||||
isSelected
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -18,11 +18,30 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { ConditionNode, ConditionTree, Operator, OperatorListItem } from 'app/src/types/Conditions';
|
||||
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
|
||||
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue';
|
||||
import { IActionProperty } from 'src/types/ActionTypes';
|
||||
|
||||
type Props = {
|
||||
props?: {
|
||||
name: string;
|
||||
modelValue?: {
|
||||
app: {
|
||||
id: string;
|
||||
name: string;
|
||||
},
|
||||
fields: {
|
||||
type: string;
|
||||
label: string;
|
||||
code: string;
|
||||
}[]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FieldInput',
|
||||
@@ -32,7 +51,7 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
context: {
|
||||
type: Array<IActionProperty>,
|
||||
type: Array<Props>,
|
||||
default: '',
|
||||
},
|
||||
displayName: {
|
||||
@@ -59,42 +78,17 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: 'field'
|
||||
},
|
||||
connectProps:{
|
||||
type:Object,
|
||||
default:()=>({})
|
||||
},
|
||||
onlySourceSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
operatorList: {
|
||||
type: Array,
|
||||
},
|
||||
inputConfig: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
left: {
|
||||
canInput: false,
|
||||
buttonsConfig: [
|
||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
||||
]
|
||||
},
|
||||
right: {
|
||||
canInput: true,
|
||||
buttonsConfig: [
|
||||
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
||||
]
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
let source = reactive(props.connectProps["source"]);
|
||||
if(!source){
|
||||
source = props.context.find(element => element.props.name === 'sources');
|
||||
}
|
||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||
|
||||
if (source) {
|
||||
if (props.sourceType === 'field') {
|
||||
@@ -104,8 +98,6 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
provide('leftDynamicItemConfig', props.inputConfig.left);
|
||||
provide('rightDynamicItemConfig', props.inputConfig.right);
|
||||
provide('Operator', props.operatorList);
|
||||
|
||||
const btnDisable = computed(() => {
|
||||
@@ -136,8 +128,7 @@ export default defineComponent({
|
||||
const isSetted = ref(props.modelValue && props.modelValue !== '');
|
||||
|
||||
const conditionString = computed(() => {
|
||||
const condiStr= tree.buildConditionString(tree.root);
|
||||
return condiStr==='()'?'(条件なし)':condiStr;
|
||||
return tree.buildConditionString(tree.root);
|
||||
});
|
||||
|
||||
const showDg = () => {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<template>
|
||||
<div class="q-my-md" v-bind="$attrs">
|
||||
<q-field :label="displayName" labelColor="primary" stack-label
|
||||
v-model="mappingProps"
|
||||
:rules="rulesExp"
|
||||
ref="fieldRef"
|
||||
>
|
||||
<div>
|
||||
<q-field :label="displayName" labelColor="primary" stack-label>
|
||||
<template v-slot:control>
|
||||
<q-card flat class="full-width">
|
||||
<q-card-actions vertical>
|
||||
@@ -20,76 +16,55 @@
|
||||
</q-card>
|
||||
</template>
|
||||
</q-field>
|
||||
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="55vw" min-height="60vh">
|
||||
<div class="">
|
||||
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="50vw" min-height="60vh">
|
||||
|
||||
<div class="q-mx-md">
|
||||
<div class="row q-col-gutter-x-xs flex-center">
|
||||
<div class="col-5">
|
||||
<div class="q-mx-xs">ソース</div>
|
||||
<div class="q-mx-xs">From</div>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
</div>
|
||||
<!-- <div class="col-1">
|
||||
</div> -->
|
||||
<div class="col-5">
|
||||
<div class="row justify-between q-mr-md">
|
||||
<div class="">{{ sourceApp?.name }}</div>
|
||||
<q-btn outline color="primary" size="xs" label="最新のフィールドを取得する"
|
||||
@click="() => updateFields(sourceAppId!)" />
|
||||
</div>
|
||||
<div class="q-mx-xs">To</div>
|
||||
</div>
|
||||
<div class="col-1 q-pl-sm">
|
||||
キー
|
||||
<div class="col-1"><q-btn flat round dense icon="add" size="sm" @click="addMappingObject" />
|
||||
</div>
|
||||
</div>
|
||||
<q-virtual-scroll style="max-height: 60vh;" :items="mappingProps.data" separator v-slot="{ item, index }">
|
||||
<!-- <div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id"> -->
|
||||
<div class="row q-pa-sm q-col-gutter-x-md flex-center">
|
||||
<div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id">
|
||||
<div class="row q-col-gutter-x-xs flex-center">
|
||||
<div class="col-5">
|
||||
<ConditionObject :config="config" v-model="item.from" :disabled="item.disabled"
|
||||
:label="item.disabled ? '「Lookup」によってロックされる' : undefined" />
|
||||
<ConditionObject v-model="item.from" />
|
||||
</div>
|
||||
<div class="col-1">
|
||||
</div>
|
||||
<!-- <div class="col-1">
|
||||
</div> -->
|
||||
<div class="col-5">
|
||||
<q-field v-model="item.vName" type="text" outlined dense :disable="item.disabled" >
|
||||
<!-- <template v-slot:append>
|
||||
<q-field v-model="item.vName" type="text" outlined dense>
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" class="cursor-pointer"
|
||||
@click="() => { mappingProps[index].to.isDialogVisible = true }" />
|
||||
</template> -->
|
||||
</template>
|
||||
<template v-slot:control>
|
||||
<div class="self-center full-width no-outline" tabindex="0"
|
||||
<div class="self-center full-width no-outline" tabindex="0"
|
||||
v-if="item.to.app?.name && item.to.fields?.length > 0 && item.to.fields[0].label">
|
||||
{{ `${item.to.fields[0].label}` }}
|
||||
<span class="text-red" v-if="item.to.fields[0].required">*</span>
|
||||
<q-tooltip class="bg-yellow-2 text-black shadow-4" >
|
||||
<div>アプリ : {{ item.to.app.name }}</div>
|
||||
<div>フィールドのコード : {{ item.to.fields[0].code }}</div>
|
||||
<div>フィールドのタイプ : {{ item.to.fields[0].type }}</div>
|
||||
<div v-if="item.to.fields[0].required">必須項目</div>
|
||||
<!-- <div>フィールド : {{ item.to.fields[0] }}</div>
|
||||
<div>フィールド : {{ item.isKey }}</div> -->
|
||||
</q-tooltip>
|
||||
{{ `${item.to.app?.name} : ${item.to.fields[0].label}` }}
|
||||
</div>
|
||||
</template>
|
||||
</q-field>
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<q-checkbox size="sm" v-model="item.isKey" :disable="item.disabled" />
|
||||
<!-- <q-btn flat round dense icon="delete" size="sm" @click="() => deleteMappingObject(index)" /> -->
|
||||
<q-btn flat round dense icon="delete" size="sm" @click="() => deleteMappingObject(index)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<show-dialog v-model:visible="mappingProps.data[index].to.isDialogVisible" name="フィールド一覧"
|
||||
<show-dialog v-model:visible="mappingProps[index].to.isDialogVisible" name="フィールド一覧"
|
||||
@close="closeToDg" ref="fieldDlg">
|
||||
<FieldSelect v-if="onlySourceSelect" ref="fieldDlg" name="フィールド" :appId="sourceAppId" not_page
|
||||
:selectedFields="mappingProps.data[index].to.fields"
|
||||
:updateSelects="(fields) => { mappingProps.data[index].to.fields = fields; mappingProps.data[index].to.app = sourceApp }">
|
||||
:selectedFields="mappingProps[index].to.fields"
|
||||
:updateSelects="(fields) => { mappingProps[index].to.fields = fields; mappingProps[index].to.app = sourceApp }">
|
||||
</FieldSelect>
|
||||
<AppFieldSelectBox v-else v-model:selectedField="mappingProps.data[index].to" />
|
||||
<AppFieldSelectBox v-else v-model:selectedField="mappingProps[index].to" />
|
||||
</show-dialog>
|
||||
<!-- </div> -->
|
||||
</q-virtual-scroll>
|
||||
|
||||
<div class="q-mt-lg q-ml-md row ">
|
||||
<q-checkbox size="sm" v-model="mappingProps.createWithNull" label="キーが存在しない場合は新規に作成され、存在する場合はデータが更新されます。" />
|
||||
</div>
|
||||
</div>
|
||||
</show-dialog>
|
||||
@@ -98,15 +73,14 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { computed, defineComponent, watch, isRef, reactive, ref, watchEffect } from 'vue';
|
||||
import { computed, defineComponent, reactive, ref, watchEffect } from 'vue';
|
||||
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
|
||||
import ShowDialog from '../ShowDialog.vue';
|
||||
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
||||
import FieldSelect from '../FieldSelect.vue';
|
||||
import { IApp, IField } from './AppFieldSelect.vue';
|
||||
import { api } from 'boot/axios';
|
||||
import IAppFields from './AppFieldSelect.vue';
|
||||
|
||||
type ContextProps = {
|
||||
type Props = {
|
||||
props?: {
|
||||
name: string;
|
||||
modelValue?: {
|
||||
@@ -117,25 +91,15 @@ type ContextProps = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
interface IMappingSetting {
|
||||
data: IMappingValueType[];
|
||||
createWithNull: boolean;
|
||||
}
|
||||
|
||||
interface IMappingValueType {
|
||||
type ValueType = {
|
||||
id: string;
|
||||
from: { sharedText?: string };
|
||||
to: {
|
||||
app?: IApp,
|
||||
fields: IField[],
|
||||
from: object;
|
||||
to: typeof IAppFields & {
|
||||
isDialogVisible: boolean;
|
||||
};
|
||||
isKey: boolean;
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
const blackListLabelName = ['レコード番号', '作業者', '更新者', '更新日時', '作成日時', '作成者']
|
||||
const defaultMappingProp = () => ({ id: uuidv4(), to: { app: {}, fields: [], isDialogVisible: false } });
|
||||
|
||||
export default defineComponent({
|
||||
name: 'DataMapping',
|
||||
@@ -148,7 +112,7 @@ export default defineComponent({
|
||||
},
|
||||
props: {
|
||||
context: {
|
||||
type: Array<ContextProps>,
|
||||
type: Array<Props>,
|
||||
default: '',
|
||||
},
|
||||
displayName: {
|
||||
@@ -160,7 +124,7 @@ export default defineComponent({
|
||||
default: '',
|
||||
},
|
||||
modelValue: {
|
||||
type: Object as () => IMappingSetting,
|
||||
type: Object as () => ValueType[],
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
@@ -169,154 +133,70 @@ export default defineComponent({
|
||||
onlySourceSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const fieldRef=ref();
|
||||
const closeDg = () => {
|
||||
emit('update:modelValue', mappingProps
|
||||
);
|
||||
}
|
||||
|
||||
const closeToDg = () => {
|
||||
emit('update:modelValue', mappingProps
|
||||
);
|
||||
}
|
||||
|
||||
const mappingProps: ValueType[] = props.modelValue
|
||||
? props.modelValue
|
||||
: reactive([defaultMappingProp()]);
|
||||
|
||||
|
||||
const deleteMappingObject = (index: number) => mappingProps.length === 1
|
||||
? mappingProps.splice(0, mappingProps.length, defaultMappingProp())
|
||||
: mappingProps.splice(index, 1);
|
||||
|
||||
const mappingObjectsInputDisplay = computed(() =>
|
||||
mappingProps ?
|
||||
mappingProps
|
||||
.filter(item => item.from?.name && item.to.fields?.length > 0)
|
||||
.map(item => {
|
||||
const name = typeof item.from?.name === 'string'
|
||||
? item.from.name
|
||||
: item.from?.name.name;
|
||||
return `[${name}] - (${item.to.app?.name} : ${item.to.fields[0].label})`;
|
||||
})
|
||||
: []
|
||||
);
|
||||
|
||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||
|
||||
const sourceApp = computed(() => source?.props?.modelValue?.app);
|
||||
|
||||
const sourceAppId = computed(() => sourceApp.value?.id);
|
||||
|
||||
//ルール設定
|
||||
const checkMapping = (val:IMappingSetting)=>{
|
||||
if(!val || !val.data){
|
||||
return false;
|
||||
}
|
||||
console.log(val);
|
||||
const mappingDatas = val.data.filter(item=>item.from?.sharedText && item.to.fields?.length > 0);
|
||||
return mappingDatas.length>0;
|
||||
}
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
|
||||
const requiredExp = props.required ? [((val: any) => checkMapping(val) || errmsg)] : [];
|
||||
const rulesExp = [...requiredExp, ...customExp];
|
||||
|
||||
// const mappingProps = ref(props.modelValue?.data ?? []);
|
||||
|
||||
// const createWithNull = ref(props.modelValue?.createWithNull ?? false);
|
||||
|
||||
const mappingProps = reactive<IMappingSetting>({
|
||||
data:props.modelValue?.data ?? [],
|
||||
createWithNull:props.modelValue?.createWithNull ?? false
|
||||
});
|
||||
|
||||
const closeDg = () => {
|
||||
fieldRef.value.validate();
|
||||
emit('update:modelValue',mappingProps);
|
||||
}
|
||||
|
||||
const closeToDg = () => {
|
||||
emit('update:modelValue',mappingProps);
|
||||
}
|
||||
|
||||
// 外部ソースコンポーネントの appid をリッスンし、変更されたときに現在のコンポーネントを更新します
|
||||
watch(() => sourceAppId.value, async (newId,) => {
|
||||
if (!newId) return;
|
||||
updateFields(newId)
|
||||
})
|
||||
|
||||
const updateFields = async (sourceAppId: string) => {
|
||||
const ktAppFields = await api.get('api/v1/appfields', {
|
||||
params: {
|
||||
app: sourceAppId
|
||||
}
|
||||
}).then(res => {
|
||||
return Object.values(res.data.properties)
|
||||
// kintoneのデフォルトの非表示フィールドフィルタリング
|
||||
.filter(f => !blackListLabelName.find(label => f.label === label))
|
||||
.map(f => ({ name: f.label, objectType: 'field', ...f }))
|
||||
.map(f => {
|
||||
// 更新前の値を求める
|
||||
const beforeData = mappingProps.data.find(m => m.to.fields[0].code === f.code)
|
||||
return {
|
||||
id: uuidv4(),
|
||||
from: beforeData?.from ?? {}, // 以前のデータを入力します
|
||||
to: {
|
||||
app: sourceApp.value,
|
||||
fields: [f],
|
||||
isDialogVisible: false
|
||||
},
|
||||
isKey: beforeData?.isKey ?? false, // 以前のデータを入力します
|
||||
disabled: false
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 「ルックアップ」によってロックされているフィールドを検索する
|
||||
const lookupFixedField = ktAppFields
|
||||
.filter(field => field.to.fields[0].lookup !== undefined)
|
||||
.flatMap(field => field.to.fields[0].lookup.fieldMappings.map((m) => m.field))
|
||||
|
||||
// 「ルックアップ」でロックされたビューコンポーネントを非対話型に設定します
|
||||
if (lookupFixedField.length > 0) {
|
||||
ktAppFields.filter(f => lookupFixedField.includes(f.to.fields[0].code)).forEach(f => f.disabled = true)
|
||||
}
|
||||
|
||||
mappingProps.data = ktAppFields
|
||||
}
|
||||
|
||||
const mappingObjectsInputDisplay = computed(() =>
|
||||
(mappingProps.data && Array.isArray(mappingProps.data)) ?
|
||||
mappingProps.data
|
||||
.filter(item => item.from?.sharedText && item.to.fields?.length > 0)
|
||||
.map(item => {
|
||||
return `field(${item.to.app?.id},${item.to.fields[0].label}) = ${item.from.sharedText} `;
|
||||
})
|
||||
: []
|
||||
);
|
||||
|
||||
const btnDisable = computed(() => props.onlySourceSelect ? !(source?.props?.modelValue?.app?.id) : false);
|
||||
|
||||
//集計処理方法
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', mappingProps);
|
||||
});
|
||||
|
||||
return {
|
||||
uuidv4,
|
||||
dgIsShow: ref(false),
|
||||
fieldRef,
|
||||
closeDg,
|
||||
toDgIsShow: ref(false),
|
||||
closeToDg,
|
||||
mappingProps,
|
||||
updateFields,
|
||||
// addMappingObject: () => mappingProps.push(defaultMappingProp()),
|
||||
// deleteMappingObject,
|
||||
addMappingObject: () => mappingProps.push(defaultMappingProp()),
|
||||
deleteMappingObject,
|
||||
mappingObjectsInputDisplay,
|
||||
sourceApp,
|
||||
sourceAppId,
|
||||
btnDisable,
|
||||
rulesExp,
|
||||
checkMapping,
|
||||
config: {
|
||||
canInput: false,
|
||||
buttonsConfig: [
|
||||
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||
{ label: '変数', color: 'green', type: 'VariableAdd', editable: false },
|
||||
]
|
||||
}
|
||||
btnDisable
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
</script>
|
||||
<style lang="scss"></style>
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<q-field :label="displayName" labelColor="primary" stack-label
|
||||
v-model="processingProps"
|
||||
:rules="rulesExp"
|
||||
lazy-rules="ondemand"
|
||||
ref="fieldRef"
|
||||
>
|
||||
<q-field :label="displayName" labelColor="primary" stack-label>
|
||||
<template v-slot:control>
|
||||
<q-card flat class="full-width">
|
||||
<q-card-actions vertical>
|
||||
@@ -45,7 +40,7 @@
|
||||
<div class="col-5">
|
||||
<ConditionObject v-model="item.field" />
|
||||
</div>
|
||||
<div class="col-2 q-pa-sm">
|
||||
<div class="col-2">
|
||||
<q-select v-model="item.logicalOperator" :options="logicalOperators" outlined dense></q-select>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
@@ -83,8 +78,14 @@ type Props = {
|
||||
|
||||
type ProcessingObjectType = {
|
||||
field?: {
|
||||
sharedText: string;
|
||||
objectType: 'field';
|
||||
name: string | {
|
||||
name: string;
|
||||
};
|
||||
objectType: string;
|
||||
type: string;
|
||||
code: string;
|
||||
label: string;
|
||||
noLabel: boolean;
|
||||
};
|
||||
logicalOperator?: string;
|
||||
vName?: string;
|
||||
@@ -125,19 +126,9 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const fieldRef=ref();
|
||||
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||
|
||||
if (source) {
|
||||
@@ -148,61 +139,44 @@ export default defineComponent({
|
||||
}
|
||||
return null;
|
||||
}));
|
||||
provide('sourceApp', computed(() => source.props?.modelValue?.app?.id));
|
||||
}
|
||||
|
||||
const actionName = props.context.find(element => element?.props?.name === 'displayName')
|
||||
|
||||
const processingProps: ValueType = props.modelValue && props.modelValue.vars
|
||||
? reactive(props.modelValue)
|
||||
? props.modelValue
|
||||
: reactive({
|
||||
name: '',
|
||||
actionName: actionName?.props?.modelValue as string,
|
||||
displayName: '結果(戻り値)',
|
||||
vars: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
field:{
|
||||
objectType:'field',
|
||||
sharedText:''
|
||||
}
|
||||
}]
|
||||
vars: [{ id: uuidv4() }]
|
||||
});
|
||||
|
||||
const closeDg = () => {
|
||||
fieldRef.value.validate();
|
||||
emit('update:modelValue', processingProps);
|
||||
emit('update:modelValue', processingProps
|
||||
);
|
||||
}
|
||||
|
||||
const processingObjects = processingProps.vars;
|
||||
|
||||
const deleteProcessingObject = (index: number) => {
|
||||
if(processingObjects.length >0){
|
||||
processingObjects.splice(index, 1);
|
||||
}
|
||||
if(processingObjects.length===0){
|
||||
addProcessingObject();
|
||||
}
|
||||
}
|
||||
const deleteProcessingObject = (index: number) => processingObjects.length === 1
|
||||
? processingObjects.splice(0, processingObjects.length, { id: uuidv4() })
|
||||
: processingObjects.splice(index, 1);
|
||||
|
||||
const processingObjectsInputDisplay = computed(() =>
|
||||
processingObjects ?
|
||||
processingObjects
|
||||
.filter(item => item.field && item.logicalOperator && item.vName)
|
||||
.map(item => {
|
||||
return`var(${processingProps.name}.${item.vName}) = ${item.field?.sharedText}`
|
||||
const name = typeof item.field?.name === 'string'
|
||||
? item.field.name
|
||||
: item.field?.name.name;
|
||||
return item.logicalOperator.operator!==''?
|
||||
`${processingProps.name}.${item.vName} = ${item.logicalOperator.operator}(${name})`
|
||||
:`${processingProps.name}.${item.vName} = ${name}`
|
||||
})
|
||||
: []
|
||||
);
|
||||
|
||||
const addProcessingObject=()=>{
|
||||
processingObjects.push({
|
||||
id: uuidv4(),
|
||||
field:{
|
||||
objectType:'field',
|
||||
sharedText:''
|
||||
}
|
||||
});
|
||||
}
|
||||
//集計処理方法
|
||||
const logicalOperators = ref([
|
||||
{
|
||||
@@ -234,24 +208,6 @@ export default defineComponent({
|
||||
"label": "最初の値"
|
||||
}
|
||||
]);
|
||||
const checkInput=(val:ValueType)=>{
|
||||
if(!val){
|
||||
return false;
|
||||
}
|
||||
if(!val.name){
|
||||
return "集計結果の変数名を入力してください";
|
||||
}
|
||||
if(!val.vars || val.vars.length==0){
|
||||
return "集計処理を設定してください";
|
||||
}
|
||||
if(val.vars.some((x)=>!x.vName)){
|
||||
return "集計結果変数名を入力してください";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const requiredExp = props.required ? [(val: any) => checkInput(val)] : [];
|
||||
const rulesExp = [...requiredExp, ...customExp];
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', processingProps);
|
||||
@@ -262,12 +218,10 @@ export default defineComponent({
|
||||
closeDg,
|
||||
processingObjects,
|
||||
processingProps,
|
||||
addProcessingObject,
|
||||
addProcessingObject: () => processingObjects.push({ id: uuidv4() }),
|
||||
deleteProcessingObject,
|
||||
logicalOperators,
|
||||
processingObjectsInputDisplay,
|
||||
rulesExp,
|
||||
fieldRef
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" label-color="primary" mask="date" :rules="rulesExp" stack-label>
|
||||
<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">
|
||||
@@ -43,32 +43,16 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const selectedDate = ref(props.modelValue);
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const requiredExp = props.required?[((val:any)=>!!val||`${props.displayName}が必須です。`),'date']:['date'];
|
||||
const rulesExp=[...requiredExp,...customExp];
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedDate.value);
|
||||
});
|
||||
|
||||
return {
|
||||
selectedDate,
|
||||
rulesExp
|
||||
selectedDate
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-input :label="displayName" v-model="inputValue" label-color="primary"
|
||||
:placeholder="placeholder"
|
||||
:rules="rulesExp"
|
||||
stack-label>
|
||||
<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>
|
||||
@@ -43,29 +40,12 @@ export default defineComponent({
|
||||
connectProps:{
|
||||
type:Object,
|
||||
default:undefined
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
setup(props , { emit }) {
|
||||
const inputValue = ref(props.modelValue);
|
||||
const store = useFlowEditorStore();
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を入力してください。`;
|
||||
const requiredExp = props.required ? [((val: any) => !!val || errmsg)] : [];
|
||||
const rulesExp = [...requiredExp, ...customExp];
|
||||
const addButtonEvent=()=>{
|
||||
const eventId =store.currentFlow?.getRoot()?.name;
|
||||
if(eventId===undefined){return;}
|
||||
@@ -81,22 +61,22 @@ export default defineComponent({
|
||||
if(store.eventTree.findEventById(addEventId)){
|
||||
return;
|
||||
}
|
||||
customEvents.events.push(new kintoneEvent(
|
||||
displayName,
|
||||
addEventId,
|
||||
customButtonId,
|
||||
'DELETABLE'
|
||||
));
|
||||
customEvents.events.push(
|
||||
new kintoneEvent(
|
||||
displayName,
|
||||
addEventId,
|
||||
customButtonId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', inputValue.value);
|
||||
});
|
||||
|
||||
return {
|
||||
inputValue,
|
||||
addButtonEvent,
|
||||
rulesExp
|
||||
addButtonEvent
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
|
||||
:bottom-slots="!isSelected"
|
||||
:rules="rulesExp"
|
||||
>
|
||||
: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>
|
||||
@@ -17,15 +18,8 @@
|
||||
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
||||
</template>
|
||||
</q-field>
|
||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" min-width="400px">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
||||
<template v-slot:before>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<field-select ref="appDg" name="フィールド" :type="selectType" :appId="store.appInfo?.appId" :selectedFields="selectedFields" :fieldTypes="fieldTypes" :filter="filter"></field-select>
|
||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" widht="400px">
|
||||
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
|
||||
</show-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -60,14 +54,6 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
selectType:{
|
||||
type:String,
|
||||
default:'single'
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: '',
|
||||
@@ -76,35 +62,16 @@ export default defineComponent({
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const appDg = ref();
|
||||
const show = ref(false);
|
||||
const selectedField = ref(props.modelValue);
|
||||
const selectedFields =computed(()=>!selectedField.value?[]: [selectedField.value]);
|
||||
const store = useFlowEditorStore();
|
||||
const isSelected = computed(() => {
|
||||
return selectedField.value !== null && typeof selectedField.value === 'object' && ('name' in selectedField.value)
|
||||
});
|
||||
//ルール設定
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||
const requiredExp = props.required ? [((val: any) => (!!val && typeof val==='object' && !!val.name) || errmsg)] : [];
|
||||
const rulesExp = [...requiredExp, ...customExp];
|
||||
|
||||
const showDg = () => {
|
||||
show.value = true;
|
||||
@@ -127,10 +94,7 @@ export default defineComponent({
|
||||
showDg,
|
||||
closeDg,
|
||||
selectedField,
|
||||
isSelected,
|
||||
filter:ref(''),
|
||||
selectedFields,
|
||||
rulesExp
|
||||
isSelected
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { kMaxLength } from 'buffer';
|
||||
import { defineComponent, ref, watchEffect, computed } from 'vue';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -32,10 +33,6 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
fieldTypes:{
|
||||
type:Array,
|
||||
default:()=>[]
|
||||
},
|
||||
hint: {
|
||||
type: String,
|
||||
default: '',
|
||||
@@ -49,16 +46,8 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
modelValue: {
|
||||
type: null as any,
|
||||
// type: Any,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
@@ -82,11 +71,8 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
// const inputValue = ref(props.modelValue);
|
||||
const rulesExp = props.rules === undefined ? null : eval(props.rules);
|
||||
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
||||
const rulesExp=[...requiredExp,...customExp];
|
||||
// const finalValue = computed(() => {
|
||||
// return props.name !== 'verName' ? inputValue.value : {
|
||||
// name: inputValue.value,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-input :label="displayName" label-color="primary" v-model="inputValue"
|
||||
:placeholder="placeholder"
|
||||
:rules="rulesExp"
|
||||
autogrow
|
||||
stack-label />
|
||||
<q-input :label="displayName" label-color="primary" v-model="inputValue" :placeholder="placeholder" autogrow
|
||||
stack-label />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,34 +32,17 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
const inputValue = ref(props.modelValue);
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
||||
const rulesExp=[...requiredExp,...customExp];
|
||||
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', inputValue.value);
|
||||
});
|
||||
|
||||
return {
|
||||
inputValue,
|
||||
rulesExp
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
0
frontend/src/components/right/MultiFieldInput.vue
Normal file
0
frontend/src/components/right/MultiFieldInput.vue
Normal file
@@ -49,14 +49,6 @@ export default defineComponent({
|
||||
type:String,
|
||||
default:undefined
|
||||
},
|
||||
required:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
modelValue: {
|
||||
type: [Number , String],
|
||||
default: undefined
|
||||
@@ -65,10 +57,23 @@ export default defineComponent({
|
||||
|
||||
setup(props, { emit }) {
|
||||
const numValue = ref(props.modelValue);
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
||||
const rulesExp=[...requiredExp,...customExp];
|
||||
const rulesExp = props.rules===undefined?null : eval(props.rules);
|
||||
const isError = computed(()=>{
|
||||
const val = numValue.value;
|
||||
if (val === undefined) {
|
||||
return false;
|
||||
}
|
||||
const numVal = typeof val === "string" ? parseInt(val) : val;
|
||||
// Ensure parsed value is a valid number
|
||||
if (isNaN(numVal)) {
|
||||
return true;
|
||||
}
|
||||
// Check against min and max boundaries, if defined
|
||||
if ((props.min !== undefined && numVal < props.min) || (props.max !== undefined && numVal > props.max)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
watchEffect(()=>{
|
||||
emit("update:modelValue",numValue.value);
|
||||
|
||||
@@ -24,7 +24,6 @@ import NumInput from './NumInput.vue';
|
||||
import DataProcessing from './DataProcessing.vue';
|
||||
import DataMapping from './DataMapping.vue';
|
||||
import AppSelect from './AppSelect.vue';
|
||||
import CascadingDropDown from './CascadingDropDown.vue';
|
||||
import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -42,8 +41,7 @@ export default defineComponent({
|
||||
NumInput,
|
||||
DataProcessing,
|
||||
DataMapping,
|
||||
AppSelect,
|
||||
CascadingDropDown
|
||||
AppSelect
|
||||
},
|
||||
props: {
|
||||
nodeProps: {
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
elevated
|
||||
overlay
|
||||
>
|
||||
<q-form @submit="save" autocomplete="off" class="full-height">
|
||||
<q-card class="column" style="max-width: 300px;min-height: 100%">
|
||||
<q-card-section>
|
||||
<div class="text-h6">{{ actionNode?.subTitle }}:設定</div>
|
||||
@@ -22,17 +21,16 @@
|
||||
|
||||
<q-card-actions align="right" class="bg-white text-teal">
|
||||
<q-btn flat label="キャンセル" @click="cancel" outline dense padding="none sm" color="primary"/>
|
||||
<q-btn flat label="更新" type="submit" outline dense padding="none sm" color="primary" />
|
||||
<q-btn flat label="更新" @click="save" outline dense padding="none sm" color="primary" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-form>
|
||||
</q-drawer>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { ref,defineComponent, PropType ,watchEffect} from 'vue'
|
||||
import PropertyList from 'components/right/PropertyList.vue';
|
||||
import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
|
||||
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: {
|
||||
@@ -49,28 +47,14 @@ import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
|
||||
}
|
||||
},
|
||||
emits: [
|
||||
'update:drawerRight',
|
||||
'saveActionProps'
|
||||
'update:drawerRight'
|
||||
],
|
||||
setup(props,{emit}) {
|
||||
const showPanel =ref(props.drawerRight);
|
||||
|
||||
const cloneProps = (actionProps:IActionProperty[]):IActionProperty[]|null=>{
|
||||
if(!actionProps){
|
||||
return null;
|
||||
}
|
||||
const json=JSON.stringify(actionProps);
|
||||
return JSON.parse(json);
|
||||
}
|
||||
|
||||
const actionProps =ref(cloneProps(props.actionNode?.actionProps));
|
||||
|
||||
const showPanel =ref(props.drawerRight);
|
||||
const actionProps =ref(props.actionNode?.actionProps);
|
||||
watchEffect(() => {
|
||||
if(showPanel.value!==undefined){
|
||||
showPanel.value = props.drawerRight;
|
||||
}
|
||||
showPanel.value = props.drawerRight;
|
||||
actionProps.value= cloneProps(props.actionNode?.actionProps);
|
||||
actionProps.value= props.actionNode?.actionProps;
|
||||
});
|
||||
|
||||
const cancel = async() =>{
|
||||
@@ -80,8 +64,7 @@ import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
|
||||
|
||||
const save = async () =>{
|
||||
showPanel.value=false;
|
||||
emit('saveActionProps', actionProps.value);
|
||||
emit('update:drawerRight',false );
|
||||
emit('update:drawerRight',false )
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<div v-bind="$attrs">
|
||||
<q-select v-model="selectedValue" :use-chips="multiple" :label="displayName" label-color="primary"
|
||||
:options="options"
|
||||
stack-label
|
||||
:rules="rulesExp"
|
||||
<q-select v-model="selectedValue" :use-chips="multiple" :label="displayName" label-color="primary" :options="options" stack-label
|
||||
:multiple="multiple"/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -32,22 +29,9 @@ export default defineComponent({
|
||||
default:'',
|
||||
},
|
||||
modelValue: {
|
||||
type: [Array,String],
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
//例:[val=>!!val ||'入力してください']
|
||||
rules: {
|
||||
type: String,
|
||||
default: undefined
|
||||
},
|
||||
required:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
},
|
||||
requiredMessage: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const selectedValue = ref(props.modelValue);
|
||||
@@ -57,14 +41,10 @@ export default defineComponent({
|
||||
watchEffect(() => {
|
||||
emit('update:modelValue', selectedValue.value);
|
||||
});
|
||||
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
|
||||
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
||||
const rulesExp=[...requiredExp,...customExp];
|
||||
|
||||
return {
|
||||
selectedValue,
|
||||
multiple,
|
||||
rulesExp
|
||||
multiple
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,28 +2,40 @@
|
||||
<q-layout view="lHh Lpr lFf">
|
||||
<q-header elevated>
|
||||
<q-toolbar>
|
||||
<q-btn flat dense round icon="menu" aria-label="Menu" @click="toggleLeftDrawer" />
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
round
|
||||
icon="menu"
|
||||
aria-label="Menu"
|
||||
@click="toggleLeftDrawer"
|
||||
/>
|
||||
<q-toolbar-title>
|
||||
{{ productName }}
|
||||
<q-badge align="top" outline>V{{ version }}</q-badge>
|
||||
</q-toolbar-title>
|
||||
<domain-selector></domain-selector>
|
||||
<q-btn flat round dense icon="logout" @click="authStore.logout()" />
|
||||
<q-btn flat round dense icon="logout" @click="authStore.logout()"/>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
|
||||
<q-drawer :model-value="authStore.LeftDrawer" :show-if-above="false" bordered>
|
||||
<q-drawer
|
||||
:model-value="authStore.toggleLeftDrawer"
|
||||
:show-if-above="false"
|
||||
bordered
|
||||
>
|
||||
<q-list>
|
||||
<q-item-label header>
|
||||
メニュー
|
||||
<q-item-label
|
||||
header
|
||||
>
|
||||
関連リンク
|
||||
</q-item-label>
|
||||
|
||||
<EssentialLink v-for="link in essentialLinks" :key="link.title" v-bind="link" />
|
||||
<div v-if="isAdmin()">
|
||||
<EssentialLink v-for="link in adminLinks" :key="link.title" v-bind="link" />
|
||||
</div>
|
||||
<EssentialLink v-for="link in domainLinks" :key="link.title" v-bind="link" />
|
||||
|
||||
<EssentialLink
|
||||
v-for="link in essentialLinks"
|
||||
:key="link.title"
|
||||
v-bind="link"
|
||||
/>
|
||||
</q-list>
|
||||
</q-drawer>
|
||||
|
||||
@@ -34,7 +46,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
|
||||
import DomainSelector from 'components/DomainSelector.vue';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
@@ -42,97 +54,107 @@ import { useAuthStore } from 'stores/useAuthStore';
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const essentialLinks: EssentialLinkProps[] = [
|
||||
{
|
||||
{
|
||||
title: 'ホーム',
|
||||
caption: '設計書から導入する',
|
||||
caption: 'home',
|
||||
icon: 'home',
|
||||
link: '/',
|
||||
target: '_self'
|
||||
target:'_self'
|
||||
},
|
||||
// {
|
||||
// title: 'フローエディター',
|
||||
// caption: 'イベントを設定する',
|
||||
// icon: 'account_tree',
|
||||
// link: '/#/FlowChart',
|
||||
// target: '_self'
|
||||
// },
|
||||
{
|
||||
title: 'アプリ管理',
|
||||
caption: 'アプリを管理する',
|
||||
icon: 'widgets',
|
||||
link: '/#/app',
|
||||
target: '_self'
|
||||
title: 'フローエディター',
|
||||
caption: 'flowChart',
|
||||
icon: 'account_tree',
|
||||
link: '/#/FlowChart',
|
||||
target:'_self'
|
||||
},
|
||||
// {
|
||||
// title: '条件エディター',
|
||||
// caption: 'condition',
|
||||
// icon: 'tune',
|
||||
// link: '/#/condition',
|
||||
// target:'_self'
|
||||
// },
|
||||
{
|
||||
title: '',
|
||||
isSeparator: true
|
||||
title: '条件エディター',
|
||||
caption: 'condition',
|
||||
icon: 'tune',
|
||||
link: '/#/condition',
|
||||
target:'_self'
|
||||
},
|
||||
// {
|
||||
// title:'Kintone ポータル',
|
||||
// caption:'Kintone',
|
||||
// icon:'cloud_queue',
|
||||
// link:'https://mfu07rkgnb7c.cybozu.com/k/#/portal'
|
||||
// },
|
||||
// {
|
||||
// title:'CUSTOMINE',
|
||||
// caption:'gusuku',
|
||||
// link:'https://app-customine.gusuku.io/drive.html',
|
||||
// icon:'settings_suggest'
|
||||
// },
|
||||
// {
|
||||
// title:'Kintone API ドキュメント',
|
||||
// caption:'Kintone API',
|
||||
// link:'https://cybozu.dev/ja/kintone/docs/',
|
||||
// icon:'help_outline'
|
||||
// },
|
||||
{
|
||||
title:'',
|
||||
isSeparator:true
|
||||
},
|
||||
{
|
||||
title:'Kintone ポータル',
|
||||
caption:'Kintone',
|
||||
icon:'cloud_queue',
|
||||
link:'https://mfu07rkgnb7c.cybozu.com/k/#/portal'
|
||||
},
|
||||
{
|
||||
title:'CUSTOMINE',
|
||||
caption:'gusuku',
|
||||
link:'https://app-customine.gusuku.io/drive.html',
|
||||
icon:'settings_suggest'
|
||||
},
|
||||
{
|
||||
title:'Kintone API ドキュメント',
|
||||
caption:'Kintone API',
|
||||
link:'https://cybozu.dev/ja/kintone/docs/',
|
||||
icon:'help_outline'
|
||||
},
|
||||
{
|
||||
title:'',
|
||||
isSeparator:true
|
||||
},
|
||||
{
|
||||
title: 'Docs',
|
||||
caption: 'quasar.dev',
|
||||
icon: 'school',
|
||||
link: 'https://quasar.dev'
|
||||
},
|
||||
{
|
||||
title: 'Icons',
|
||||
caption: 'Material Icons',
|
||||
icon: 'insert_emoticon',
|
||||
link: 'https://fonts.google.com/icons?selected=Material+Icons:insert_emoticon:'
|
||||
},
|
||||
{
|
||||
title: 'Github',
|
||||
caption: 'github.com/quasarframework',
|
||||
icon: 'code',
|
||||
link: 'https://github.com/quasarframework'
|
||||
},
|
||||
{
|
||||
title: 'Discord Chat Channel',
|
||||
caption: 'chat.quasar.dev',
|
||||
icon: 'chat',
|
||||
link: 'https://chat.quasar.dev'
|
||||
},
|
||||
{
|
||||
title: 'Forum',
|
||||
caption: 'forum.quasar.dev',
|
||||
icon: 'record_voice_over',
|
||||
link: 'https://forum.quasar.dev'
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
caption: '@quasarframework',
|
||||
icon: 'rss_feed',
|
||||
link: 'https://twitter.quasar.dev'
|
||||
},
|
||||
{
|
||||
title: 'Facebook',
|
||||
caption: '@QuasarFramework',
|
||||
icon: 'public',
|
||||
link: 'https://facebook.quasar.dev'
|
||||
},
|
||||
{
|
||||
title: 'Quasar Awesome',
|
||||
caption: 'Community Quasar projects',
|
||||
icon: 'favorite',
|
||||
link: 'https://awesome.quasar.dev'
|
||||
}
|
||||
];
|
||||
|
||||
const domainLinks: EssentialLinkProps[] = [
|
||||
{
|
||||
title: 'ドメイン管理',
|
||||
caption: 'kintoneのドメイン設定',
|
||||
icon: 'domain',
|
||||
link: '/#/domain',
|
||||
target: '_self'
|
||||
},
|
||||
// {
|
||||
// title: 'ドメイン適用',
|
||||
// caption: 'ユーザー使用可能なドメインの設定',
|
||||
// icon: 'assignment_ind',
|
||||
// link: '/#/userDomain',
|
||||
// target: '_self'
|
||||
// },
|
||||
];
|
||||
|
||||
const adminLinks: EssentialLinkProps[] = [
|
||||
{
|
||||
title: 'ユーザー管理',
|
||||
caption: 'ユーザーを管理する',
|
||||
icon: 'manage_accounts',
|
||||
link: '/#/user',
|
||||
target: '_self'
|
||||
},
|
||||
]
|
||||
|
||||
const version = process.env.version;
|
||||
const productName = process.env.productName;
|
||||
|
||||
onMounted(() => {
|
||||
authStore.toggleLeftMenu();
|
||||
});
|
||||
|
||||
function toggleLeftDrawer() {
|
||||
authStore.toggleLeftMenu();
|
||||
}
|
||||
function isAdmin(){
|
||||
const permission = authStore.permissions;
|
||||
return permission === 'admin'
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<div class="q-gutter-sm row items-start">
|
||||
<q-breadcrumbs>
|
||||
<q-breadcrumbs-el icon="widgets" label="アプリ管理" />
|
||||
</q-breadcrumbs>
|
||||
</div>
|
||||
<q-table title="Treats" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
||||
|
||||
<template v-slot:top>
|
||||
<q-btn color="primary" :disable="loading" label="新規" @click="showAddAppDialog" />
|
||||
<q-space />
|
||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<template v-slot:body-cell-url="prop">
|
||||
<q-td :props="prop">
|
||||
<a :href="prop.row.url" target="_blank" :title="prop.row.name" >
|
||||
{{ prop.row.url }}
|
||||
</a>
|
||||
</q-td>
|
||||
</template>
|
||||
<template v-slot:body-cell-actions="p">
|
||||
<q-td :props="p">
|
||||
<q-btn-group flat>
|
||||
<q-btn flat color="primary" padding="xs" size="1em" icon="edit_note" @click="toEditFlowPage(p.row)" />
|
||||
<q-btn disabled flat color="primary" padding="xs" size="1em" icon="history" @click="showHistory(p.row)" />
|
||||
<q-btn disabled flat color="negative" padding="xs" size="1em" icon="delete_outline" @click="removeRow(p.row)" />
|
||||
</q-btn-group>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
|
||||
<show-dialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeSelectAppDialog" min-width="50vw" min-height="50vh" :ok-btn-auto-close="false" :ok-btn-loading="isAdding">
|
||||
<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>
|
||||
<app-select-box ref="appDialog" name="アプリ" type="single" :filter="filter" :filterInitRowsFunc="filterInitRows" />
|
||||
</show-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, reactive } from 'vue';
|
||||
import { api } from 'boot/axios';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import { router } from 'src/router';
|
||||
import { date } from 'quasar'
|
||||
import { IManagedApp } from 'src/types/AppTypes';
|
||||
import ShowDialog from 'src/components/ShowDialog.vue';
|
||||
import AppSelectBox from 'src/components/AppSelectBox.vue';
|
||||
|
||||
interface IAppDisplay{
|
||||
id:string;
|
||||
sortId: number;
|
||||
name:string;
|
||||
url:string;
|
||||
user:string;
|
||||
version:string;
|
||||
updatetime:string;
|
||||
}
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const numberStringSorting = (a: string, b: string) => parseInt(a, 10) - parseInt(b, 10);
|
||||
|
||||
const columns = [
|
||||
{ name: 'id', label: 'アプリID', field: 'id', align: 'left', sortable: true, sort: numberStringSorting },
|
||||
{ name: 'name', label: 'アプリ名', field: 'name', align: 'left', sortable: true },
|
||||
{ name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true },
|
||||
{ name: 'user', label: '最後更新者', field: 'user', align: 'left', sortable: true},
|
||||
{ name: 'updatetime', label: '最後更新日', field: 'updatetime', align: 'left', sortable: true},
|
||||
{ name: 'version', label: 'バージョン', field: 'version', align: 'left', sortable: true, sort: numberStringSorting },
|
||||
{ name: 'actions', label: '操作', field: 'actions' }
|
||||
];
|
||||
|
||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
||||
const loading = ref(false);
|
||||
const filter = ref('');
|
||||
const rows = ref<IAppDisplay[]>([]);
|
||||
const rowIds = new Set<string>();
|
||||
|
||||
const store = useFlowEditorStore();
|
||||
const appDialog = ref();
|
||||
const showSelectApp=ref(false);
|
||||
const isAdding = ref(false);
|
||||
|
||||
const getApps = async () => {
|
||||
loading.value = true;
|
||||
rowIds.clear();
|
||||
const result = await api.get('api/apps');
|
||||
rows.value = result.data.map((item: IManagedApp) => {
|
||||
rowIds.add(item.appid);
|
||||
return appToAppDisplay(item)
|
||||
}).sort((a: IAppDisplay, b: IAppDisplay) => a.sortId - b.sortId); // set default order
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
authStore.setLeftMenu(false);
|
||||
await getApps();
|
||||
});
|
||||
|
||||
watch(() => authStore.currentDomain.id, async () => {
|
||||
await getApps();
|
||||
});
|
||||
|
||||
const filterInitRows = (row: {id: string}) => {
|
||||
return !rowIds.has(row.id);
|
||||
}
|
||||
|
||||
const showAddAppDialog = () => {
|
||||
showSelectApp.value = true;
|
||||
}
|
||||
|
||||
const closeSelectAppDialog = async (val: 'OK'|'Cancel') => {
|
||||
showSelectApp.value = true;
|
||||
if (val == 'OK' && appDialog.value.selected[0]) {
|
||||
isAdding.value = true;
|
||||
toEditFlowPage(appDialog.value.selected[0]);
|
||||
}
|
||||
showSelectApp.value = false;
|
||||
isAdding.value = false;
|
||||
}
|
||||
|
||||
const removeRow = (app:IAppDisplay) => {
|
||||
return
|
||||
}
|
||||
|
||||
const showHistory = (app:IAppDisplay) => {
|
||||
return
|
||||
}
|
||||
|
||||
const appToAppDisplay = (app: IManagedApp) => {
|
||||
return {
|
||||
id: app.appid,
|
||||
sortId: parseInt(app.appid, 10),
|
||||
name: app.appname,
|
||||
url: `${app.domainurl}/k/${app.appid}`,
|
||||
user: `${app.updateuser.first_name} ${app.updateuser.last_name}` ,
|
||||
updatetime:date.formatDate(app.update_time, 'YYYY/MM/DD HH:mm'),
|
||||
version: app.version
|
||||
}
|
||||
}
|
||||
|
||||
const toEditFlowPage = (app:IAppDisplay) => {
|
||||
store.setApp({
|
||||
appId: app.id,
|
||||
name: app.name
|
||||
});
|
||||
store.selectFlow(undefined);
|
||||
router.push('/FlowChart/' + app.id);
|
||||
};
|
||||
</script>
|
||||
@@ -3,7 +3,11 @@
|
||||
<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 absolute-full" style="padding:15px">
|
||||
<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>
|
||||
@@ -12,50 +16,14 @@
|
||||
<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-dropdown color="primary" label="保存" icon="save" :loading="saveLoading" >
|
||||
<q-list>
|
||||
<q-item clickable v-close-popup @click="onSaveFlow">
|
||||
<q-item-section avatar >
|
||||
<q-icon name="save" color="primary"></q-icon>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>選択中フローの保存</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item clickable v-close-popup @click="onSaveAllFlow">
|
||||
<q-item-section avatar>
|
||||
<q-icon name="collections_bookmark" color="accent"></q-icon>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>一括保存</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
<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="{'left': fixedLeftPosition}"
|
||||
:style="[drawerLeft?{'left':'300px'}:{'left':'0px'}]"
|
||||
@click="drawerLeft=!drawerLeft" class="expand" />
|
||||
<q-breadcrumbs v-if="store.appInfo" class="fixed q-pl-md"
|
||||
:style="{'left': fixedLeftPosition}">
|
||||
<q-breadcrumbs-el icon="widgets" label="アプリ管理" to="/app" />
|
||||
<q-breadcrumbs-el>
|
||||
<template v-slot>
|
||||
<a class="full-width" :href="!store.appInfo?'':`${authStore.currentDomain.kintoneUrl}/k/${store.appInfo?.appId}`" target="_blank" title="Kiontoneへ">
|
||||
{{ store.appInfo?.name }}
|
||||
<q-icon
|
||||
class="q-ma-xs"
|
||||
name="open_in_new"
|
||||
color="grey-9"
|
||||
/>
|
||||
</a>
|
||||
</template>
|
||||
</q-breadcrumbs-el>
|
||||
</q-breadcrumbs>
|
||||
<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"
|
||||
@@ -63,7 +31,7 @@
|
||||
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
|
||||
</div>
|
||||
</div>
|
||||
<PropertyPanel :actionNode="store.activeNode" v-model:drawerRight="drawerRight" @save-action-props="onSaveActionProps"></PropertyPanel>
|
||||
<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>
|
||||
@@ -73,42 +41,33 @@
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<action-select ref="appDg" name="model" :filter="filter" type="single" @clearFilter="onClearFilter" ></action-select>
|
||||
<action-select ref="appDg" name="model" :filter="filter" type="single"></action-select>
|
||||
</ShowDialog>
|
||||
<q-inner-loading
|
||||
:showing="initLoading"
|
||||
color="primary"
|
||||
label="読み込み中..."
|
||||
/>
|
||||
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes';
|
||||
import { IManagedApp } from 'src/types/AppTypes';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
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 initLoading = ref(true);
|
||||
const drawerLeft = ref(false);
|
||||
const $q = useQuasar();
|
||||
const store = useFlowEditorStore();
|
||||
const authStore = useAuthStore();
|
||||
const route = useRoute()
|
||||
|
||||
const appDg = ref();
|
||||
const prevNodeIfo = ref({
|
||||
@@ -120,7 +79,10 @@ 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();
|
||||
});
|
||||
@@ -132,9 +94,6 @@ const minPanelWidth=computed(()=>{
|
||||
return "300px";
|
||||
}
|
||||
});
|
||||
const fixedLeftPosition = computed(()=>{
|
||||
return drawerLeft.value?"300px":"0px";
|
||||
});
|
||||
|
||||
const addNode = (node: IActionNode, inputPoint: string) => {
|
||||
if (drawerRight.value) {
|
||||
@@ -234,24 +193,13 @@ const onDeploy = async () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const onSaveActionProps=(props:IActionProperty[])=>{
|
||||
if(store.activeNode){
|
||||
store.activeNode.actionProps=props;
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
caption: "通知",
|
||||
message: `${store.activeNode?.subTitle}の属性を設定しました。(保存はされていません)`
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onSaveFlow = async () => {
|
||||
const targetFlow = store.selectedFlow;
|
||||
if (targetFlow === undefined) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: 'エラー',
|
||||
message: `選択中のフローがありません。`
|
||||
caption: "エラー",
|
||||
message: `編集中のフローがありません。`
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -273,90 +221,38 @@ const onSaveFlow = async () => {
|
||||
message: `${targetFlow.getRoot()?.subTitle}のフローの設定の保存が失敗しました。`
|
||||
})
|
||||
}
|
||||
}
|
||||
/**
|
||||
* すべてフローの設定を保存する
|
||||
*/
|
||||
const onSaveAllFlow= async ()=>{
|
||||
try{
|
||||
const targetFlows = store.eventTree.findAllFlows();
|
||||
if (!targetFlows || targetFlows.length === 0 ) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: 'エラー',
|
||||
message: `設定されたフローがありません。`
|
||||
});
|
||||
return;
|
||||
}
|
||||
saveLoading.value = true;
|
||||
for(const flow of targetFlows ){
|
||||
const isNew = flow.id === '';
|
||||
if(isNew && flow.actionNodes.length===1){
|
||||
continue;
|
||||
}
|
||||
await store.saveFlow(flow);
|
||||
}
|
||||
$q.notify({
|
||||
type: 'positive',
|
||||
caption: "通知",
|
||||
message: `すべてのフロー設定を保存しました。`
|
||||
});
|
||||
saveLoading.value = false;
|
||||
}catch (error) {
|
||||
console.error(error);
|
||||
saveLoading.value = false;
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: "エラー",
|
||||
message: `フローの設定の保存が失敗しました。`
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const fetchData = async () => {
|
||||
initLoading.value = true;
|
||||
if (store.appInfo === undefined && route?.params?.id !== undefined) {
|
||||
const { appid, appname } = await fetchAppById(route.params.id as string);
|
||||
store.setApp({
|
||||
appId: appid,
|
||||
name: appname
|
||||
});
|
||||
};
|
||||
await store.loadFlow();
|
||||
initLoading.value = false
|
||||
drawerLeft.value = true;
|
||||
}
|
||||
|
||||
const fetchAppById = async(id: string) => {
|
||||
try {
|
||||
const result = await api.get('api/apps');
|
||||
return result.data.find((item: IManagedApp) => item.appid === id ) as IManagedApp;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
const result = await api.get(`api/v1/app?app=${id}`);
|
||||
const data = result?.data;
|
||||
if (data?.message) {
|
||||
$q.notify({
|
||||
type: 'negative',
|
||||
caption: "エラー",
|
||||
message: data.message
|
||||
});
|
||||
}
|
||||
return { appid: data.appId, appname: data.name };
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
const onClearFilter=()=>{
|
||||
filter.value='';
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
authStore.setLeftMenu(false);
|
||||
authStore.toggleLeftMenu();
|
||||
fetchData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.app-selector {
|
||||
padding: 15px;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.flowchart {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
@@ -1,95 +1,54 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<div class="q-gutter-sm row items-start">
|
||||
<q-breadcrumbs>
|
||||
<q-breadcrumbs-el icon="domain" label="ドメイン管理" />
|
||||
</q-breadcrumbs>
|
||||
</div>
|
||||
<q-table title="Treats" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
||||
<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 filled debounce="300" v-model="filter" placeholder="検索">
|
||||
<q-input borderless dense debounce="300" color="primary" v-model="filter">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-cell-actions="p">
|
||||
<q-td :props="p">
|
||||
<q-btn-group flat>
|
||||
<q-btn flat color="primary" padding="xs" size="1em" icon="edit_note" @click="editRow(p.row)" />
|
||||
<q-btn flat color="negative" padding="xs" size="1em" icon="delete_outline" @click="removeRow(p.row)" />
|
||||
</q-btn-group>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
</q-table>
|
||||
|
||||
<q-dialog :model-value="show" persistent>
|
||||
<q-card style="min-width: 36em">
|
||||
<q-form class="q-gutter-md" @submit="onSubmit" autocomplete="off">
|
||||
<q-card-section>
|
||||
<div class="text-h6 q-ma-sm">Kintone Account</div>
|
||||
</q-card-section>
|
||||
<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-mt-none">
|
||||
<div class="q-gutter-lg">
|
||||
<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 v-model="tenantid" label="テナントID" hint="テナントIDを入力してください。" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'テナントIDを入力してください。']" />
|
||||
<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="name" label="環境名 *" hint="kintoneの環境名を入力してください" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'kintoneの環境名を入力してください。']" />
|
||||
<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 filled type="url" v-model="url" label="Kintone url" hint="KintoneのURLを入力してください" lazy-rules
|
||||
:rules="[val => val && val.length > 0, isDomain || 'KintoneのURLを入力してください']" />
|
||||
|
||||
<q-input filled v-model="kintoneuser" label="ログイン名 *" hint="Kintoneのログイン名を入力してください" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'Kintoneのログイン名を入力してください']" autocomplete="new-password" />
|
||||
|
||||
<q-input v-if="isCreate" v-model="kintonepwd" filled :type="isPwd ? 'password' : 'text'"
|
||||
hint="パスワード" label="パスワード" :disable="!isCreate" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'Please type something']" autocomplete="new-password">
|
||||
<template v-slot:append>
|
||||
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer"
|
||||
@click="isPwd = !isPwd" />
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<div class="q-gutter-y-md" v-if="!isCreate">
|
||||
<q-separator />
|
||||
|
||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||
<q-item-section>
|
||||
<q-item-label>パスワードリセット</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
<q-toggle v-model="resetPsw" @update:model-value="updateResetPsw" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-input v-model="kintonepwd" filled :type="isPwd ? 'password' : 'text'" hint="パスワードを入力してください"
|
||||
label="パスワード" :disable="!resetPsw" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'Please type something']" autocomplete="new-password">
|
||||
<template v-slot:append>
|
||||
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer"
|
||||
@click="isPwd = !isPwd" />
|
||||
</template>
|
||||
</q-input>
|
||||
<!-- <q-btn label="asdf"/> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</q-card-section>
|
||||
<q-card-actions align="right" class="text-primary q-mb-md q-mx-sm">
|
||||
<q-btn label="保存" type="submit" color="primary" />
|
||||
<q-btn label="キャンセル" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
<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>
|
||||
@@ -97,7 +56,7 @@
|
||||
<q-dialog v-model="confirm" persistent>
|
||||
<q-card>
|
||||
<q-card-section class="row items-center">
|
||||
<q-icon name="warning" color="warning" size="2em" />
|
||||
<q-avatar icon="confirm" color="primary" text-color="white" />
|
||||
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
||||
</q-card-section>
|
||||
|
||||
@@ -114,68 +73,66 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, reactive } from 'vue';
|
||||
import { api } from 'boot/axios';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
|
||||
const columns = [
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||
{ name: 'id' },
|
||||
{
|
||||
name: 'tenantid',
|
||||
required: true,
|
||||
label: 'テナントID',
|
||||
label: 'Tenant',
|
||||
align: 'left',
|
||||
field: row => row.tenantid,
|
||||
format: val => `${val}`,
|
||||
align: 'left',
|
||||
sortable: true
|
||||
},
|
||||
{ name: 'name', label: '環境名', field: 'name', align: 'left', sortable: true },
|
||||
{ name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true },
|
||||
{ name: 'user', label: 'ログイン名', field: 'user', align: 'left', },
|
||||
{ name: 'actions', label: '操作', field: 'actions' }
|
||||
{ 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 pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
||||
|
||||
const loading = ref(false);
|
||||
const filter = ref('');
|
||||
const rows = ref([]);
|
||||
const show = ref(false);
|
||||
const confirm = ref(false);
|
||||
const resetPsw = ref(false);
|
||||
|
||||
const tenantid = ref(authStore.currentDomain.id);
|
||||
const selected = ref([]);
|
||||
const tenantid = ref('');
|
||||
const name = ref('');
|
||||
const url = ref('');
|
||||
const isPwd = ref(true);
|
||||
const kintoneuser = ref('');
|
||||
const kintonepwd = ref('');
|
||||
const kintonepwdBK = ref('');
|
||||
const isCreate = ref(true);
|
||||
|
||||
let editId = ref(0);
|
||||
|
||||
const getDomain = async () => {
|
||||
loading.value = true;
|
||||
const userId = authStore.userId;
|
||||
const result = await api.get(`api/domain?userId=${userId}`);
|
||||
rows.value = result.data.map((item) => {
|
||||
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();
|
||||
await getDomain();
|
||||
})
|
||||
|
||||
// emulate fetching data from server
|
||||
const addRow = () => {
|
||||
// editId.value
|
||||
onReset();
|
||||
editId.value
|
||||
show.value = true;
|
||||
}
|
||||
|
||||
const removeRow = (row) => {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -184,11 +141,14 @@ const deleteDomain = () => {
|
||||
getDomain();
|
||||
})
|
||||
editId.value = 0;
|
||||
|
||||
selected.value = [];
|
||||
};
|
||||
|
||||
const editRow = (row) => {
|
||||
isCreate.value = false
|
||||
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;
|
||||
@@ -198,16 +158,6 @@ const editRow = (row) => {
|
||||
isPwd.value = true;
|
||||
show.value = true;
|
||||
};
|
||||
|
||||
const updateResetPsw = (value: boolean) => {
|
||||
if (value === true) {
|
||||
kintonepwd.value = ''
|
||||
isPwd.value = true
|
||||
} else {
|
||||
kintonepwd.value = kintonepwdBK.value
|
||||
}
|
||||
}
|
||||
|
||||
const closeDg = () => {
|
||||
show.value = false;
|
||||
onReset();
|
||||
@@ -221,7 +171,7 @@ const onSubmit = () => {
|
||||
'name': name.value,
|
||||
'url': url.value,
|
||||
'kintoneuser': kintoneuser.value,
|
||||
'kintonepwd': isCreate.value || resetPsw.value ? kintonepwd.value : ''
|
||||
'kintonepwd': kintonepwd.value
|
||||
}).then(() => {
|
||||
getDomain();
|
||||
closeDg();
|
||||
@@ -242,7 +192,7 @@ const onSubmit = () => {
|
||||
onReset();
|
||||
})
|
||||
}
|
||||
|
||||
selected.value = [];
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
@@ -252,7 +202,5 @@ const onReset = () => {
|
||||
kintonepwd.value = '';
|
||||
isPwd.value = true;
|
||||
editId.value = 0;
|
||||
isCreate.value = true;
|
||||
resetPsw.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,43 +1,127 @@
|
||||
<template>
|
||||
<!-- <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 class="q-pa-lg">
|
||||
<div class="q-gutter-sm row items-start">
|
||||
<q-breadcrumbs>
|
||||
<q-breadcrumbs-el icon="assignment_ind" label="ドメイン適用" />
|
||||
</q-breadcrumbs>
|
||||
</div>
|
||||
<q-table grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" row-key="name"
|
||||
:filter="userDomainTableFilter" virtual-scroll v-model:pagination="pagination">
|
||||
</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>
|
||||
|
||||
<q-btn class="q-mx-none" color="primary" label="追加" @click="clickAddDomain()" />
|
||||
|
||||
<div class="q-pa-md q-gutter-sm">
|
||||
<q-btn color="primary" label="追加" @click="newDomain()" dense />
|
||||
</div>
|
||||
<q-space />
|
||||
<div class="row q-gutter-md">
|
||||
<q-item v-if="authStore.permissions === 'admin'" tag="label" dense @click="clickSwitchUser()">
|
||||
<q-item-section>
|
||||
<q-item-label>適用するユーザ : </q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
{{ currentUserName }}
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-input borderless dense filled debounce="300" v-model="userDomainTableFilter" placeholder="Search">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
<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:header>
|
||||
<div style="height: 1dvh">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:item="props">
|
||||
<div class="q-pa-sm">
|
||||
<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">
|
||||
@@ -46,73 +130,40 @@
|
||||
</div>
|
||||
<div class="q-table__grid-item-row">
|
||||
<div class="q-table__grid-item-title">URL</div>
|
||||
<div class="q-table__grid-item-value" style="width: 22rem;">{{ props.row.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">
|
||||
<div style="width: 98%;">
|
||||
<div class="row items-center justify-between">
|
||||
<div class="q-table__grid-item-value"
|
||||
:class="isActive(props.row.id) ? 'text-positive' : 'text-negative'">{{
|
||||
isActive(props.row.id)?'既定':'' }}</div>
|
||||
<div class="col-auto">
|
||||
<q-btn v-if="!isActive(props.row.id)" flat
|
||||
@click="activeDomain(props.row.id)">既定にする</q-btn>
|
||||
<q-btn flat @click="clickDeleteConfirm(props.row)">削除</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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="showAddDomainDg" name="ドメイン" @close="addUserDomainFinished">
|
||||
<domain-select ref="addDomainRef" name="ドメイン" type="multiple"></domain-select>
|
||||
<show-dialog v-model:visible="show" name="ドメイン" @close="closeDg" width="350px">
|
||||
<domain-select ref="domainDg" name="ドメイン" type="multiple"></domain-select>
|
||||
</show-dialog>
|
||||
|
||||
<show-dialog v-model:visible="showSwitchUserDd" name="ドメイン" minWidth="35vw" @close="switchUserFinished">
|
||||
<template v-slot:toolbar>
|
||||
<q-input dense placeholder="検索" v-model="switchUserFilter">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
<div class="q-gutter-md">
|
||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||
<q-item-section>
|
||||
<q-item-label>他のユーザーを選択する</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
<q-toggle v-model="useOtherUser" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<div v-if="useOtherUser">
|
||||
<user-list ref="switchUserRef" name="ドメイン" :filter="switchUserFilter" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</show-dialog>
|
||||
|
||||
<q-dialog v-model="showDeleteConfirm" persistent>
|
||||
<q-dialog v-model="confirm" persistent>
|
||||
<q-card>
|
||||
<q-card-section class="row items-center">
|
||||
<div class="q-ma-sm q-mt-md">
|
||||
<q-icon name="warning" color="warning" size="2em" />
|
||||
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
||||
</div>
|
||||
<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="deleteDomainFinished()" />
|
||||
<q-btn flat label="OK" color="primary" v-close-popup @click = "deleteDomain()"/>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
@@ -120,117 +171,100 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { api } from 'boot/axios';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
|
||||
import { useQuasar } from 'quasar'
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import ShowDialog from 'components/ShowDialog.vue';
|
||||
import DomainSelect from 'components/DomainSelect.vue';
|
||||
import UserList from 'components/UserList.vue';
|
||||
import { useAuthStore } from 'stores/useAuthStore';
|
||||
|
||||
const authStore = useAuthStore();
|
||||
const pagination = ref({ sortBy: 'id', rowsPerPage: 0 });
|
||||
const rows = ref([] as any[]);
|
||||
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: '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 userDomainTableFilter = ref();
|
||||
{ name: 'active', field: 'active'}
|
||||
]
|
||||
|
||||
const currentUserName = ref('');
|
||||
const useOtherUser = ref(false);
|
||||
const otherUserId = ref('');
|
||||
const rows = ref([] as any[]);
|
||||
|
||||
let editId = ref(0);
|
||||
const isActive = (id:number) =>{
|
||||
if(id == activedomainid.value)
|
||||
return "Active";
|
||||
else
|
||||
return "Inactive";
|
||||
}
|
||||
|
||||
const showAddDomainDg = ref(false);
|
||||
const addDomainRef = ref();
|
||||
|
||||
const clickAddDomain = () => {
|
||||
const newDomain = () => {
|
||||
editId.value = 0;
|
||||
showAddDomainDg.value = true;
|
||||
show.value = true;
|
||||
};
|
||||
|
||||
const addUserDomainFinished = (val: string) => {
|
||||
if (val == 'OK') {
|
||||
let dodmainids = [];
|
||||
let domains = JSON.parse(JSON.stringify(addDomainRef.value.selected));
|
||||
for (var key in domains) {
|
||||
dodmainids.push(domains[key].id);
|
||||
}
|
||||
api.post(`api/domain/${useOtherUser.value ? otherUserId.value : authStore.userId}`, dodmainids)
|
||||
.then(() => { getDomain(useOtherUser.value ? otherUserId.value : undefined); });
|
||||
}
|
||||
|
||||
const activeDomain = (id:number) => {
|
||||
api.put(`api/activedomain/`+ id).then(() =>{
|
||||
getDomain();
|
||||
})
|
||||
};
|
||||
|
||||
const showDeleteConfirm = ref(false);
|
||||
|
||||
const clickDeleteConfirm = (row: any) => {
|
||||
showDeleteConfirm.value = true;
|
||||
const deleteConfirm = (row:object) => {
|
||||
confirm.value = true;
|
||||
editId.value = row.id;
|
||||
};
|
||||
|
||||
const deleteDomainFinished = () => {
|
||||
api.delete(`api/domain/${editId.value}/${useOtherUser.value ? otherUserId.value : authStore.userId}`).then(() => {
|
||||
getDomain(useOtherUser.value ? otherUserId.value : undefined);
|
||||
})
|
||||
const deleteDomain = () => {
|
||||
api.delete(`api/domain/`+ editId.value+'/1').then(() =>{
|
||||
getDomain();
|
||||
})
|
||||
editId.value = 0;
|
||||
};
|
||||
|
||||
const activeDomain = (id: number) => {
|
||||
api.put(`api/activedomain/${id}${useOtherUser.value ? `?userId=${otherUserId.value}` : ''}`)
|
||||
.then(() => { getDomain(useOtherUser.value ? otherUserId.value : undefined); })
|
||||
};
|
||||
|
||||
let activeDomainId = ref(0);
|
||||
|
||||
const isActive = computed(() => (id: number) => {
|
||||
return id == activeDomainId.value;
|
||||
});
|
||||
|
||||
|
||||
const showSwitchUserDd = ref(false);
|
||||
const switchUserRef = ref();
|
||||
const switchUserFilter = ref('')
|
||||
|
||||
const clickSwitchUser = () => {
|
||||
showSwitchUserDd.value = true;
|
||||
useOtherUser.value = false;
|
||||
};
|
||||
|
||||
const switchUserFinished = async (val: string) => {
|
||||
const closeDg = (val:string) => {
|
||||
if (val == 'OK') {
|
||||
if (useOtherUser.value) {
|
||||
const user = switchUserRef.value.selected[0]
|
||||
currentUserName.value = user.email;
|
||||
otherUserId.value = user.id
|
||||
await getDomain(user.id)
|
||||
} else {
|
||||
currentUserName.value = authStore.userInfo.email
|
||||
await getDomain();
|
||||
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 (userId? : string) => {
|
||||
const resp = await api.get(`api/activedomain${useOtherUser.value ? `?userId=${otherUserId.value}` : ''}`);
|
||||
activeDomainId.value = resp?.data?.id;
|
||||
const domainResult = userId ? await api.get(`api/domain?userId=${userId}`) : 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 }
|
||||
});
|
||||
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 () => {
|
||||
currentUserName.value = authStore.userInfo.email
|
||||
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>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,307 +0,0 @@
|
||||
<template>
|
||||
<div class="q-pa-md">
|
||||
<div class="q-gutter-sm row items-start">
|
||||
<q-breadcrumbs>
|
||||
<q-breadcrumbs-el icon="manage_accounts" label="ユーザー管理" />
|
||||
</q-breadcrumbs>
|
||||
</div>
|
||||
<q-table title="ユーザーリスト" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading"
|
||||
:pagination="pagination" >
|
||||
|
||||
<template v-slot:top>
|
||||
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||
<q-space />
|
||||
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search" />
|
||||
</template>
|
||||
</q-input>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-cell-status="props">
|
||||
<q-td :props="props">
|
||||
<div class="row">
|
||||
<div v-if="props.row.isActive">
|
||||
<q-chip square color="positive" text-color="white" icon="done" label="使用可能" size="sm" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<q-chip square color="negative" text-color="white" icon="block" label="使用不可" size="sm" />
|
||||
</div>
|
||||
|
||||
<q-chip v-if="props.row.isSuperuser" square color="accent" text-color="white" icon="admin_panel_settings"
|
||||
label="システム管理者" size="sm" />
|
||||
</div>
|
||||
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template v-slot:header-cell-status="p">
|
||||
<q-th :props="p">
|
||||
<div class="row items-center">
|
||||
<label class="q-mr-md">{{ p.col.label }}</label>
|
||||
<q-select v-model="statusFilter" :options="options" @update:model-value="updateStatusFilter" borderless
|
||||
dense options-dense style="font-size: 12px; padding-top: 1px;" />
|
||||
</div>
|
||||
</q-th>
|
||||
</template>
|
||||
|
||||
<template v-slot:body-cell-actions="p">
|
||||
<q-td :props="p">
|
||||
<q-btn-group flat>
|
||||
<q-btn flat color="primary" padding="xs" size="1em" icon="edit_note" @click="editRow(p.row)" />
|
||||
<q-btn flat color="negative" padding="xs" size="1em" icon="delete_outline" @click="removeRow(p.row)" />
|
||||
</q-btn-group>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
|
||||
<q-dialog :model-value="show" persistent>
|
||||
<q-card style="min-width: 36em">
|
||||
<q-form class="q-gutter-md" @submit="onSubmit" autocomplete="off">
|
||||
<q-card-section>
|
||||
<div class="text-h6 q-ma-sm">K-True Account</div>
|
||||
</q-card-section>
|
||||
|
||||
<q-card-section class="q-pt-none q-mt-none">
|
||||
<div class="q-gutter-lg">
|
||||
|
||||
<q-input filled v-model="firstName" label="氏名 *" hint="ユーザーの氏名を入力してください" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'ユーザーの氏名を入力してください。']" />
|
||||
|
||||
<q-input filled v-model="lastName" label="苗字 *" hint="ユーザーの苗字を入力してください" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'ユーザーの苗字を入力してください']" />
|
||||
|
||||
<q-input filled type="email" v-model="email" label="電子メール *" hint="電子メール、ログインとしても使用" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || '電子メールを入力してください']" autocomplete="new-password" />
|
||||
|
||||
<q-input v-if="isCreate" v-model="pwd" filled :type="isPwd ? 'password' : 'text'" hint="パスワード"
|
||||
label="パスワード" :disable="!isCreate" lazy-rules
|
||||
:rules="[val => val && val.length > 0 || 'Please type something']" autocomplete="new-password">
|
||||
<template v-slot:append>
|
||||
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer"
|
||||
@click="isPwd = !isPwd" />
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||
<q-item-section>
|
||||
<q-item-label>システム管理者</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
<q-toggle v-model="isSuperuser" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||
<q-item-section>
|
||||
<q-item-label>使用可能</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
<q-toggle v-model="isActive" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<div class="q-gutter-y-md" v-if="!isCreate">
|
||||
<q-separator />
|
||||
|
||||
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||
<q-item-section>
|
||||
<q-item-label>パスワードリセット</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
<q-toggle v-model="resetPsw" />
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-input v-model="pwd" filled :type="isPwd ? 'password' : 'text'" hint="パスワードを入力してください" label="パスワード"
|
||||
:disable="!resetPsw" lazy-rules :rules="[val => val && val.length > 0 || 'Please type something']"
|
||||
autocomplete="new-password">
|
||||
<template v-slot:append>
|
||||
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer"
|
||||
@click="isPwd = !isPwd" />
|
||||
</template>
|
||||
</q-input>
|
||||
<!-- <q-btn label="asdf"/> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</q-card-section>
|
||||
<q-card-actions align="right" class="text-primary q-mb-md q-mx-sm">
|
||||
<q-btn label="保存" type="submit" color="primary" />
|
||||
<q-btn label="キャンセル" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
</q-card>
|
||||
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="confirm" persistent>
|
||||
<q-card>
|
||||
<q-card-section class="row items-center">
|
||||
<q-icon name="warning" color="warning" size="2em" />
|
||||
<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="deleteUser()" />
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { api } from 'boot/axios';
|
||||
|
||||
const columns = [
|
||||
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||
{ name: 'firstName', label: '氏名', field: 'firstName', align: 'left', sortable: true },
|
||||
{ name: 'lastName', label: '苗字', field: 'lastName', align: 'left', sortable: true },
|
||||
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||
{ name: 'status', label: '状況', field: 'status', align: 'left' },
|
||||
{ name: 'actions', label: '操作', field: 'actions' }
|
||||
];
|
||||
|
||||
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
||||
|
||||
const loading = ref(false);
|
||||
const filter = ref('');
|
||||
const statusFilter = ref('全データ');
|
||||
const rows = ref([]);
|
||||
const show = ref(false);
|
||||
const confirm = ref(false);
|
||||
const resetPsw = ref(false);
|
||||
|
||||
const firstName = ref('');
|
||||
const lastName = ref('');
|
||||
const email = ref('');
|
||||
const isSuperuser = ref(false);
|
||||
const isActive = ref(true);
|
||||
|
||||
const isPwd = ref(true);
|
||||
const pwd = ref('');
|
||||
const isCreate = ref(true);
|
||||
let editId = ref(0);
|
||||
|
||||
const getUsers = async (filter = () => true) => {
|
||||
loading.value = true;
|
||||
const result = await api.get(`api/v1/users`);
|
||||
rows.value = result.data.map((item) => {
|
||||
return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active }
|
||||
}).filter(filter);
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
const updateStatusFilter = (status) => {
|
||||
switch (status) {
|
||||
case 'システム管理者のみ':
|
||||
getUsers((row) => row.isSuperuser)
|
||||
break;
|
||||
case '使用可能':
|
||||
getUsers((row) => row.isActive)
|
||||
break;
|
||||
case '使用不可':
|
||||
getUsers((row) => !row.isActive)
|
||||
break;
|
||||
default:
|
||||
getUsers()
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getUsers();
|
||||
})
|
||||
|
||||
const options = ['全データ', 'システム管理者のみ', '使用可能', '使用不可']
|
||||
|
||||
// emulate fetching data from server
|
||||
const addRow = () => {
|
||||
// editId.value
|
||||
onReset();
|
||||
show.value = true;
|
||||
}
|
||||
|
||||
const removeRow = (row) => {
|
||||
confirm.value = true;
|
||||
editId.value = row.id;
|
||||
}
|
||||
|
||||
const deleteUser = () => {
|
||||
api.delete(`api/v1/users/${editId.value}`).then(() => {
|
||||
getUsers();
|
||||
})
|
||||
editId.value = 0;
|
||||
};
|
||||
|
||||
const editRow = (row) => {
|
||||
isCreate.value = false
|
||||
editId.value = row.id;
|
||||
|
||||
firstName.value = row.firstName;
|
||||
lastName.value = row.lastName;
|
||||
email.value = row.email;
|
||||
pwd.value = row.password;
|
||||
|
||||
isSuperuser.value = row.isSuperuser;
|
||||
isActive.value = row.isActive;
|
||||
|
||||
isPwd.value = true;
|
||||
show.value = true;
|
||||
};
|
||||
|
||||
const closeDg = () => {
|
||||
show.value = false;
|
||||
onReset();
|
||||
}
|
||||
|
||||
const onSubmit = () => {
|
||||
if (editId.value !== 0) {
|
||||
api.put(`api/v1/users/${editId.value}`, {
|
||||
'first_name': firstName.value,
|
||||
'last_name': lastName.value,
|
||||
'is_superuser': isSuperuser.value,
|
||||
'is_active': isActive.value,
|
||||
'email': email.value,
|
||||
...(isCreate.value || resetPsw.value ? { password: pwd.value } : {})
|
||||
}).then(() => {
|
||||
getUsers();
|
||||
closeDg();
|
||||
onReset();
|
||||
})
|
||||
}
|
||||
else {
|
||||
api.post(`api/v1/users`, {
|
||||
'id': 0,
|
||||
'first_name': firstName.value,
|
||||
'last_name': lastName.value,
|
||||
'is_superuser': isSuperuser.value,
|
||||
'is_active': isActive.value,
|
||||
'email': email.value,
|
||||
'password': pwd.value
|
||||
}).then(() => {
|
||||
getUsers();
|
||||
closeDg();
|
||||
onReset();
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
firstName.value = '';
|
||||
lastName.value = '';
|
||||
email.value = '';
|
||||
pwd.value = '';
|
||||
isActive.value = true;
|
||||
isSuperuser.value = false;
|
||||
isPwd.value = true;
|
||||
editId.value = 0;
|
||||
isCreate.value = true;
|
||||
resetPsw.value = false
|
||||
}
|
||||
</script>
|
||||
@@ -6,7 +6,7 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('pages/LoginPage.vue')
|
||||
},
|
||||
{
|
||||
path:'/FlowChart/:id',
|
||||
path:'/FlowChart',
|
||||
component:()=>import('layouts/MainLayout.vue'),
|
||||
children:[
|
||||
{path:'',component:()=>import('pages/FlowChart.vue')}
|
||||
@@ -25,9 +25,7 @@ const routes: RouteRecordRaw[] = [
|
||||
// { 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: 'user', component: () => import('pages/UserManagement.vue')},
|
||||
{ path: 'app', component: () => import('pages/AppManagement.vue')},
|
||||
{ path: 'userdomain', component: () => import('pages/UserDomain.vue')},
|
||||
{ path: 'condition', component: () => import('pages/conditionPage.vue') }
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { AppInfo, IActionFlow, IActionNode } from 'src/types/ActionTypes';
|
||||
import { IKintoneEvent, KintoneEventManager, kintoneEvent } from 'src/types/KintoneEvents';
|
||||
import { IKintoneEvent, KintoneEventManager } from 'src/types/KintoneEvents';
|
||||
import { FlowCtrl } from '../control/flowctrl';
|
||||
|
||||
export interface FlowEditorState {
|
||||
@@ -11,7 +11,7 @@ export interface FlowEditorState {
|
||||
activeNode: IActionNode | undefined;
|
||||
eventTree: KintoneEventManager;
|
||||
selectedEvent: IKintoneEvent | undefined;
|
||||
expandedScreen: string[];
|
||||
expandedScreen: any[];
|
||||
}
|
||||
const flowCtrl = new FlowCtrl();
|
||||
const eventTree = new KintoneEventManager();
|
||||
@@ -62,19 +62,10 @@ export const useFlowEditorStore = defineStore('flowEditor', {
|
||||
},
|
||||
selectFlow(flow: IActionFlow | undefined) {
|
||||
this.selectedFlow = flow;
|
||||
if(flow!==undefined){
|
||||
const eventId = flow.getRoot()?.name;
|
||||
this.selectedEvent = this.eventTree.findEventById(eventId) as IKintoneEvent;
|
||||
} else {
|
||||
this.selectedEvent = undefined;
|
||||
}
|
||||
},
|
||||
setActiveNode(node: IActionNode) {
|
||||
this.activeNode = node;
|
||||
},
|
||||
setCurrentEvent(event:IKintoneEvent | undefined){
|
||||
this.selectedEvent=event;
|
||||
},
|
||||
setApp(app: AppInfo) {
|
||||
this.appInfo = app;
|
||||
},
|
||||
@@ -88,47 +79,27 @@ export const useFlowEditorStore = defineStore('flowEditor', {
|
||||
//eventTreeにバンドする
|
||||
this.eventTree.bindFlows(actionFlows);
|
||||
if (actionFlows === undefined || actionFlows.length === 0) {
|
||||
this.setFlows([]);
|
||||
this.selectFlow(undefined);
|
||||
this.expandedScreen =[];
|
||||
this.flows = [];
|
||||
this.selectedFlow = undefined;
|
||||
return;
|
||||
}
|
||||
this.setFlows(actionFlows);
|
||||
if (actionFlows && actionFlows.length > 0) {
|
||||
this.selectFlow(actionFlows[0]);
|
||||
}
|
||||
const root = actionFlows[0].getRoot();
|
||||
if (root) {
|
||||
this.setActiveNode(root);
|
||||
}
|
||||
|
||||
const expandEventIds = actionFlows.map((flow) => flow.getRoot()?.name);
|
||||
const expandScreens:string[]=[];
|
||||
expandEventIds.forEach((eventid)=>{
|
||||
const eventNode=this.eventTree.findEventById(eventid||'');
|
||||
if(eventNode){
|
||||
expandScreens.push(eventNode.parentId);
|
||||
if(eventNode.header==='DELETABLE'){
|
||||
const groupEvent = this.eventTree.findEventById(eventNode.parentId);
|
||||
if(groupEvent){
|
||||
expandScreens.push(groupEvent.parentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const expandNames = actionFlows.map((flow) => flow.getRoot()?.title);
|
||||
// const expandName =actionFlows[0].getRoot()?.title;
|
||||
this.expandedScreen = expandScreens;
|
||||
this.expandedScreen = expandNames;
|
||||
},
|
||||
/**
|
||||
* フローをDBに保存及び更新する
|
||||
*/
|
||||
async saveFlow(flow: IActionFlow):Promise<boolean> {
|
||||
async saveFlow(flow: IActionFlow) {
|
||||
const root = flow.getRoot();
|
||||
const isNew = flow.id === '';
|
||||
const jsonData = {
|
||||
flowid: isNew ? flow.createNewId() : flow.id,
|
||||
appid: this.appInfo?.appId,
|
||||
appname: this.appInfo?.name,
|
||||
eventid: root?.name,
|
||||
name: root?.subTitle,
|
||||
content: JSON.stringify(flow),
|
||||
@@ -137,31 +108,24 @@ export const useFlowEditorStore = defineStore('flowEditor', {
|
||||
if (isNew) {
|
||||
return await flowCtrl.SaveFlow(jsonData);
|
||||
} else {
|
||||
if(flow.actionNodes.length>1){
|
||||
return await flowCtrl.UpdateFlow(jsonData);
|
||||
}else{
|
||||
const eventId = flow.getRoot()?.name||'';
|
||||
const eventNode = eventTree.findEventById(eventId) as kintoneEvent;
|
||||
eventNode.flowData=undefined;
|
||||
return await flowCtrl.DeleteFlow(flow.id);
|
||||
}
|
||||
return await flowCtrl.UpdateFlow(jsonData);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
async deleteEvent(event: IKintoneEvent) {
|
||||
deleteEvent(event: IKintoneEvent) {
|
||||
const store = useFlowEditorStore();
|
||||
if (event.flowData) {
|
||||
const flow = event.flowData;
|
||||
if (flow.id !== '') {
|
||||
await flowCtrl.DeleteFlow(flow.id)
|
||||
if (this.flows) {
|
||||
this.flows = this.flows.filter((f) => f.id !== flow.id);
|
||||
}
|
||||
if (flow.id === '') {
|
||||
return;
|
||||
}
|
||||
flowCtrl.DeleteFlow(flow.id)
|
||||
eventTree.deleteEvent(event, store);
|
||||
}
|
||||
else {
|
||||
if(this.flows){
|
||||
this.flows = this.flows.filter((f) => f.id !== flow.id);
|
||||
}
|
||||
} else {
|
||||
eventTree.deleteEvent(event, store);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { store } from 'quasar/wrappers'
|
||||
import { createPinia } from 'pinia'
|
||||
import { Router } from 'vue-router';
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
|
||||
/*
|
||||
* When adding new properties to stores, you should also
|
||||
@@ -24,11 +23,10 @@ declare module 'pinia' {
|
||||
*/
|
||||
|
||||
export default store((/* { ssrContext } */) => {
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPluginPersistedstate);
|
||||
const pinia = createPinia()
|
||||
|
||||
// You can add Pinia plugins here
|
||||
// pinia.use(SomePiniaPlugin)
|
||||
|
||||
return pinia;
|
||||
});
|
||||
return pinia
|
||||
})
|
||||
|
||||
@@ -1,122 +1,91 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { api } from 'boot/axios';
|
||||
import { router } from 'src/router';
|
||||
import { IDomainInfo } from '../types/ActionTypes';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
interface UserInfo {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
import {router} from 'src/router';
|
||||
import {IDomainInfo} from '../types/ActionTypes';
|
||||
|
||||
|
||||
export interface IUserState{
|
||||
token?:string;
|
||||
returnUrl:string;
|
||||
currentDomain:IDomainInfo;
|
||||
LeftDrawer:boolean;
|
||||
}
|
||||
|
||||
export interface IUserState {
|
||||
token?: string;
|
||||
returnUrl: string;
|
||||
currentDomain: IDomainInfo;
|
||||
LeftDrawer: boolean;
|
||||
userId?: string;
|
||||
userInfo: UserInfo;
|
||||
permissions: 'admin' | 'user';
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: (): IUserState => ({
|
||||
token: '',
|
||||
returnUrl: '',
|
||||
LeftDrawer: false,
|
||||
currentDomain: {} as IDomainInfo,
|
||||
userId: '',
|
||||
userInfo: {} as UserInfo,
|
||||
permissions: 'user',
|
||||
}),
|
||||
getters: {
|
||||
toggleLeftDrawer(): boolean {
|
||||
return this.LeftDrawer;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
setLeftMenu(value:boolean){
|
||||
this.LeftDrawer=value;
|
||||
},
|
||||
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;
|
||||
const tokenJson = jwtDecode(result.data.access_token);
|
||||
this.userId = tokenJson.sub;
|
||||
this.permissions = (tokenJson as any).permissions ?? 'user';
|
||||
api.defaults.headers['Authorization'] = 'Bearer ' + this.token;
|
||||
this.currentDomain = await this.getCurrentDomain();
|
||||
this.userInfo = await this.getUserInfo();
|
||||
router.push(this.returnUrl || '/');
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return false;
|
||||
export const useAuthStore = defineStore({
|
||||
id: 'auth',
|
||||
state: ():IUserState =>{
|
||||
const token=localStorage.getItem('token')||'';
|
||||
if(token!==''){
|
||||
api.defaults.headers["Authorization"]='Bearer ' + token;
|
||||
}
|
||||
},
|
||||
async getCurrentDomain(): Promise<IDomainInfo> {
|
||||
const resp = await api.get(`api/activedomain`);
|
||||
const activedomain = resp?.data;
|
||||
return {
|
||||
id: activedomain?.id,
|
||||
domainName: activedomain?.name,
|
||||
kintoneUrl: activedomain?.url,
|
||||
};
|
||||
},
|
||||
async getUserDomains(): Promise<IDomainInfo[]> {
|
||||
const resp = await api.get(`api/domain`);
|
||||
const domains = resp.data as any[];
|
||||
return domains.map((data) => ({
|
||||
id: data.id,
|
||||
domainName: data.name,
|
||||
kintoneUrl: data.url,
|
||||
}));
|
||||
},
|
||||
async getUserInfo():Promise<UserInfo>{
|
||||
const resp = (await api.get(`api/v1/users/me`)).data;
|
||||
return {
|
||||
firstName: resp.first_name,
|
||||
lastName: resp.last_name,
|
||||
email: resp.email,
|
||||
}
|
||||
},
|
||||
logout() {
|
||||
this.token = '';
|
||||
this.currentDomain = {} as IDomainInfo; // 清空当前域
|
||||
router.push('/login');
|
||||
},
|
||||
async setCurrentDomain(domain: IDomainInfo) {
|
||||
if (domain.id === this.currentDomain.id) {
|
||||
return;
|
||||
}
|
||||
await api.put(`api/activedomain/${domain.id}`);
|
||||
this.currentDomain = domain;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
afterRestore: (ctx) => {
|
||||
api.defaults.headers['Authorization'] = 'Bearer ' + ctx.store.token;
|
||||
|
||||
//axios例外キャプチャー
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response && error.response.status === 401) {
|
||||
// 認証エラーの場合再ログインする
|
||||
console.error('(; ゚Д゚)/認証エラー(401):', error);
|
||||
ctx.store.logout();
|
||||
}
|
||||
return Promise.reject(error);
|
||||
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));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -292,11 +292,6 @@ export class ActionFlow implements IActionFlow {
|
||||
if (!targetNode) {
|
||||
return false;
|
||||
}
|
||||
if(targetNode.isRoot){
|
||||
this.actionNodes=[targetNode];
|
||||
targetNode.nextNodeIds.clear();
|
||||
return;
|
||||
}
|
||||
if (targetNode.nextNodeIds.size == 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -317,9 +312,9 @@ export class ActionFlow implements IActionFlow {
|
||||
if (!targetNode) {
|
||||
return
|
||||
}
|
||||
// if (targetNode.nextNodeIds.size == 0) {
|
||||
// return
|
||||
// }
|
||||
if (targetNode.nextNodeIds.size == 0) {
|
||||
return
|
||||
}
|
||||
for (const [, id] of targetNode.nextNodeIds) {
|
||||
this.removeAll(id);
|
||||
}
|
||||
@@ -465,7 +460,7 @@ export class ActionFlow implements IActionFlow {
|
||||
|
||||
if(prevNode.varName.modelValue ==='object'){
|
||||
console.log(prevNode);
|
||||
|
||||
|
||||
}
|
||||
|
||||
varNames.unshift({
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
interface IUser {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface IManagedApp {
|
||||
appid: string;
|
||||
appname: string;
|
||||
domainurl: string;
|
||||
version: string;
|
||||
updateuser: IUser;
|
||||
update_time: string;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
export interface IApp {
|
||||
id: string,
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface IField {
|
||||
label?:string;
|
||||
code:string;
|
||||
type?:string;
|
||||
required?:boolean;
|
||||
options?:string;
|
||||
|
||||
}
|
||||
/**
|
||||
* 選択されたフィールド
|
||||
*/
|
||||
export interface ISelectedField extends IField{
|
||||
objectType:'Field'|'RefField';
|
||||
}
|
||||
|
||||
export interface IAppFields {
|
||||
app?: IApp,
|
||||
name?:string;
|
||||
fields: IField[]
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 条件式の入力ボタンの属性定義
|
||||
*/
|
||||
export interface IButtonConfig{
|
||||
label: string;
|
||||
color: string;
|
||||
type: 'FieldAdd' | 'VariableAdd' | 'FunctionAdd';
|
||||
};
|
||||
/**
|
||||
* 条件入力項目の属性
|
||||
*/
|
||||
export interface IDynamicInputConfig{
|
||||
canInput: boolean;
|
||||
buttonsConfig: IButtonConfig[];
|
||||
}
|
||||
/**
|
||||
* 条件式入力項目の属性
|
||||
*/
|
||||
export interface ICoditionConfig{
|
||||
left:IDynamicInputConfig,
|
||||
right:IDynamicInputConfig
|
||||
}
|
||||
@@ -200,14 +200,12 @@ export class ConditionTree {
|
||||
return conditionString;
|
||||
} else {
|
||||
const condNode=node as ConditionNode;
|
||||
if (condNode.object && condNode.object.sharedText && condNode.operator ) {
|
||||
// let value=condNode.value;
|
||||
// if(value && typeof value ==='object' && ('label' in value)){
|
||||
// value =condNode.value.label;
|
||||
// }
|
||||
const rightVal = condNode.value.sharedText || '""';
|
||||
return `${condNode.object.sharedText} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} ${rightVal}`;
|
||||
// return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} '${value}'`;
|
||||
if (condNode.object && condNode.operator ) {
|
||||
let value=condNode.value;
|
||||
if(value && typeof value ==='object' && ('label' in value)){
|
||||
value =condNode.value.label;
|
||||
}
|
||||
return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} '${value}'`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
@@ -220,7 +218,7 @@ export class ConditionTree {
|
||||
if(node.type !== NodeType.Root){
|
||||
conditionString = '(';
|
||||
}
|
||||
|
||||
|
||||
const groupNode = node as GroupNode;
|
||||
for (let i = 0; i < groupNode.children.length; i++) {
|
||||
const childConditionString = this.buildConditionQueryString(groupNode.children[i]);
|
||||
@@ -239,10 +237,11 @@ export class ConditionTree {
|
||||
} else {
|
||||
const condNode=node as ConditionNode;
|
||||
if (condNode.object && condNode.operator ) {
|
||||
if (!condNode.object.code || !condNode.value.sharedText){
|
||||
return '';
|
||||
}
|
||||
return `${condNode.object.code} ${typeof condNode.operator === 'object' ? condNode.operator.value : condNode.operator} "${condNode.value.sharedText}"`;
|
||||
let value=condNode.value;
|
||||
if(value && typeof value ==='object' && ('label' in value)){
|
||||
value =condNode.value.label;
|
||||
}
|
||||
return `${condNode.object.code} ${typeof condNode.operator === 'object' ? condNode.operator.value : condNode.operator} "${value}"`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -24,12 +24,11 @@ export class kintoneEvent implements IKintoneEvent {
|
||||
}
|
||||
flowData?: IActionFlow | undefined;
|
||||
label: string;
|
||||
header :string;
|
||||
constructor(label: string, eventId: string, parentId: string,header?:string) {
|
||||
header = 'EVENT';
|
||||
constructor(label: string, eventId: string, parentId: string) {
|
||||
this.eventId = eventId;
|
||||
this.label = label;
|
||||
this.parentId = parentId;
|
||||
this.header=header?header:'EVENT';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,11 +95,21 @@ export class KintoneEventManager {
|
||||
const lastIndex = eventId.lastIndexOf('.');
|
||||
const groupId = eventId.substring(0, lastIndex);
|
||||
const eventNode = this.findEventById(groupId);
|
||||
if (eventNode && (eventNode.header === 'EVENTGROUP' || eventNode.header === 'CHANGE')) {
|
||||
if (
|
||||
eventNode &&
|
||||
(eventNode.header === 'EVENTGROUP' || eventNode.header === 'CHANGE')
|
||||
) {
|
||||
const groupEvent = eventNode as kintoneEventGroup;
|
||||
const label=flow.getRoot()?.subTitle || '';
|
||||
const newEvent = new kintoneEvent(label,eventId,groupId,'DELETABLE');
|
||||
newEvent.flowData=flow;
|
||||
|
||||
const newEvent = {
|
||||
label: flow.getRoot()?.subTitle || '',
|
||||
eventId: eventId,
|
||||
parentId: groupId,
|
||||
header: 'DELETABLE',
|
||||
hasFlow: true,
|
||||
flowData: flow,
|
||||
};
|
||||
|
||||
groupEvent.events.push(newEvent);
|
||||
}
|
||||
}
|
||||
@@ -132,31 +141,6 @@ export class KintoneEventManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
public findAllFlows():IActionFlow[]{
|
||||
const flows:IActionFlow[]=[];
|
||||
for (const screen of this.screens) {
|
||||
for (const event of screen.events) {
|
||||
if (event.header === "EVENT") {
|
||||
const eventNode = event as IKintoneEvent;
|
||||
if(eventNode.flowData!==undefined){
|
||||
flows.push(eventNode.flowData);
|
||||
}
|
||||
}else if (event.header === 'EVENTGROUP' || event.header === 'CHANGE') {
|
||||
const eventGroup = event as IKintoneEventGroup;
|
||||
eventGroup.events.forEach((ev) => {
|
||||
if (ev.header === "EVENT" || ev.header === "DELETABLE") {
|
||||
const eventNode = ev as IKintoneEvent;
|
||||
if(eventNode.flowData!==undefined){
|
||||
flows.push(eventNode.flowData);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return flows;
|
||||
}
|
||||
|
||||
public findScreen(eventId: string): IKintoneEventGroup | undefined {
|
||||
return this.screens.find((screen) => screen.eventId == eventId);
|
||||
}
|
||||
@@ -213,7 +197,7 @@ export class KintoneEventManager {
|
||||
),
|
||||
new kintoneEventGroup(
|
||||
'app.record.create.show.customButtonClick',
|
||||
'ボタンをクリックしたとき',
|
||||
'ボタンをクリックした時',
|
||||
[],
|
||||
'app.record.create'
|
||||
),
|
||||
@@ -241,7 +225,7 @@ export class KintoneEventManager {
|
||||
),
|
||||
new kintoneEventGroup(
|
||||
'app.record.detail.show.customButtonClick',
|
||||
'ボタンをクリックしたとき',
|
||||
'ボタンをクリックした時',
|
||||
[],
|
||||
'app.record.detail'
|
||||
),
|
||||
@@ -275,7 +259,7 @@ export class KintoneEventManager {
|
||||
),
|
||||
new kintoneEventGroup(
|
||||
'app.record.edit.show.customButtonClick',
|
||||
'ボタンをクリックしたとき',
|
||||
'ボタンをクリックした時',
|
||||
[],
|
||||
'app.record.edit'
|
||||
),
|
||||
@@ -297,7 +281,7 @@ export class KintoneEventManager {
|
||||
'app.record.index'
|
||||
),
|
||||
new kintoneEvent(
|
||||
'インライン編集の保存をクリックしたとき',
|
||||
'インライン編集の【保存】をクリックしたとき',
|
||||
'app.record.index.edit.submit',
|
||||
'app.record.index'
|
||||
),
|
||||
@@ -306,15 +290,15 @@ export class KintoneEventManager {
|
||||
'app.record.index.edit.submit.success',
|
||||
'app.record.index'
|
||||
),
|
||||
// new kintoneEventForChange(
|
||||
// 'app.record.index.edit.change',
|
||||
// 'インライン編集のフィールド値を変更したとき',
|
||||
// [],
|
||||
// 'app.record.index'
|
||||
// ),
|
||||
new kintoneEventForChange(
|
||||
'app.record.index.edit.change',
|
||||
'インライン編集のフィールド値を変更したとき',
|
||||
[],
|
||||
'app.record.index'
|
||||
),
|
||||
new kintoneEventGroup(
|
||||
'app.record.index.show.customButtonClick',
|
||||
'ボタンをクリックしたとき',
|
||||
'app.record.detail.show.customButtonClick',
|
||||
'ボタンをクリックした時',
|
||||
[],
|
||||
'app.record.index'
|
||||
),
|
||||
|
||||
@@ -283,11 +283,6 @@
|
||||
resolved "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.3.tgz"
|
||||
integrity sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==
|
||||
|
||||
"@types/web-bluetooth@^0.0.20":
|
||||
version "0.0.20"
|
||||
resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597"
|
||||
integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==
|
||||
|
||||
"@typescript-eslint/eslint-plugin@^5.10.0":
|
||||
version "5.61.0"
|
||||
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz"
|
||||
@@ -424,11 +419,6 @@
|
||||
resolved "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz"
|
||||
integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
|
||||
|
||||
"@vue/devtools-api@^6.6.3":
|
||||
version "6.6.3"
|
||||
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.3.tgz#b23a588154cba8986bba82b6e1d0248bde3fd1a0"
|
||||
integrity sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==
|
||||
|
||||
"@vue/reactivity-transform@3.3.4":
|
||||
version "3.3.4"
|
||||
resolved "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz"
|
||||
@@ -477,28 +467,6 @@
|
||||
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz"
|
||||
integrity sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==
|
||||
|
||||
"@vueuse/core@^10.9.0":
|
||||
version "10.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.11.1.tgz#15d2c0b6448d2212235b23a7ba29c27173e0c2c6"
|
||||
integrity sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==
|
||||
dependencies:
|
||||
"@types/web-bluetooth" "^0.0.20"
|
||||
"@vueuse/metadata" "10.11.1"
|
||||
"@vueuse/shared" "10.11.1"
|
||||
vue-demi ">=0.14.8"
|
||||
|
||||
"@vueuse/metadata@10.11.1":
|
||||
version "10.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-10.11.1.tgz#209db7bb5915aa172a87510b6de2ca01cadbd2a7"
|
||||
integrity sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==
|
||||
|
||||
"@vueuse/shared@10.11.1":
|
||||
version "10.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.11.1.tgz#62b84e3118ae6e1f3ff38f4fbe71b0c5d0f10938"
|
||||
integrity sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==
|
||||
dependencies:
|
||||
vue-demi ">=0.14.8"
|
||||
|
||||
accepts@~1.3.5, accepts@~1.3.8:
|
||||
version "1.3.8"
|
||||
resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz"
|
||||
@@ -1862,11 +1830,6 @@ jsonfile@^6.0.1:
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
jwt-decode@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b"
|
||||
integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==
|
||||
|
||||
kind-of@^6.0.2:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz"
|
||||
@@ -2249,18 +2212,13 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
pinia-plugin-persistedstate@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.2.1.tgz#66780602aecd6c7b152dd7e3ddc249a1f7a13fe5"
|
||||
integrity sha512-MK++8LRUsGF7r45PjBFES82ISnPzyO6IZx3CH5vyPseFLZCk1g2kgx6l/nW8pEBKxxd4do0P6bJw+mUSZIEZUQ==
|
||||
|
||||
pinia@^2.1.7:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.2.1.tgz#7cf860f6a23981c23e58605cee45496ce46d15d1"
|
||||
integrity sha512-ltEU3xwiz5ojVMizdP93AHi84Rtfz0+yKd8ud75hr9LVyWX2alxp7vLbY1kFm7MXFmHHr/9B08Xf8Jj6IHTEiQ==
|
||||
pinia@^2.1.6:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz"
|
||||
integrity sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==
|
||||
dependencies:
|
||||
"@vue/devtools-api" "^6.6.3"
|
||||
vue-demi "^0.14.10"
|
||||
"@vue/devtools-api" "^6.5.0"
|
||||
vue-demi ">=0.14.5"
|
||||
|
||||
postcss-selector-parser@^6.0.9:
|
||||
version "6.0.13"
|
||||
@@ -2834,10 +2792,10 @@ vite@^2.9.13:
|
||||
optionalDependencies:
|
||||
fsevents "~2.3.2"
|
||||
|
||||
vue-demi@>=0.14.8, vue-demi@^0.14.10:
|
||||
version "0.14.10"
|
||||
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.10.tgz#afc78de3d6f9e11bf78c55e8510ee12814522f04"
|
||||
integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==
|
||||
vue-demi@>=0.14.5:
|
||||
version "0.14.6"
|
||||
resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz"
|
||||
integrity sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==
|
||||
|
||||
vue-eslint-parser@^9.3.0:
|
||||
version "9.3.1"
|
||||
|
||||
6
node_modules/.package-lock.json
generated
vendored
6
node_modules/.package-lock.json
generated
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "App Builder for kintone",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
15
node_modules/.yarn-integrity
generated
vendored
15
node_modules/.yarn-integrity
generated
vendored
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"systemParams": "win32-x64-108",
|
||||
"modulesFolders": [
|
||||
"node_modules"
|
||||
],
|
||||
"flags": [],
|
||||
"linkedModules": [
|
||||
"@quasar\\quasar-ui-qactivity",
|
||||
"docs"
|
||||
],
|
||||
"topLevelPatterns": [],
|
||||
"lockfileEntries": {},
|
||||
"files": [],
|
||||
"artifacts": {}
|
||||
}
|
||||
16
plugin/kintone-addins/package-lock.json
generated
16
plugin/kintone-addins/package-lock.json
generated
@@ -8,8 +8,7 @@
|
||||
"name": "kintone-addins",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"jquery": "^3.7.1",
|
||||
"yarn": "^1.22.22"
|
||||
"jquery": "^3.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jquery": "^3.5.24",
|
||||
@@ -796,19 +795,6 @@
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/yarn": {
|
||||
"version": "1.22.22",
|
||||
"resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz",
|
||||
"integrity": "sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"yarn": "bin/yarn.js",
|
||||
"yarnpkg": "bin/yarn.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,14 @@
|
||||
"watch": "vite build --watch --mode dev",
|
||||
"server": "vite dev --mode dev",
|
||||
"ngrok": "ngrok http 4173",
|
||||
"build": "run-s b:production copyjs:windows copycss:windows",
|
||||
"build:dev": "run-s b:dev copyjs:windows copycss:windows",
|
||||
"build:linux": "run-s b:production copyjs:linux copycss:linux",
|
||||
"build": "run-s b:production copy:windows",
|
||||
"build:dev": "run-s b:dev copy:windows",
|
||||
"build:linux": "run-s b:production copy:linux",
|
||||
"build:linux-dev": "run-s b:dev copy:linux",
|
||||
"b:production": "tsc & vite build --mode production",
|
||||
"b:dev": "tsc & vite build --mode dev",
|
||||
"copyjs:windows": "xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
|
||||
"copyjs:linux": "cp -ur dist/*.js ../../backend/Temp",
|
||||
"copycss:windows": "xcopy dist\\*.css ..\\..\\backend\\Temp\\ /E /I /Y",
|
||||
"copycss:linux": "cp -ur dist/*.css ../../backend/Temp"
|
||||
"b:production": "vite build --mode production",
|
||||
"b:dev": "vite build --mode dev",
|
||||
"copy:windows": "xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
|
||||
"copy:linux": "cp -ur dist/*.js ../../backend/Temp"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jquery": "^3.5.24",
|
||||
@@ -26,13 +24,10 @@
|
||||
"sass": "^1.69.5",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5",
|
||||
"vite-plugin-checker": "^0.6.4"
|
||||
"vite-plugin-checker": "^0.6.4",
|
||||
"vite-plugin-lib-inject-css": "^2.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kintone/rest-api-client": "^5.5.2",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@types/bootstrap": "^5.2.10",
|
||||
"bootstrap": "^5.3.3",
|
||||
"jquery": "^3.7.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,10 +70,7 @@
|
||||
| placeholder | 対象項目を選択してください| 入力フィールドに表示されるプレースホルダーのテキストです。この場合は設定されていません。 |
|
||||
| hint | 説明文| 長い説明文を設定することが可能です。(markdown形式サポート予定、現在HTML可能) |
|
||||
| selectType |`single` or `multiple`| フィールド選択・他のアプリのフィールド選択の選択モードを設定する |
|
||||
| required | boolean | 必須チェックするかどうか |
|
||||
| requiredMessage| string | 必須チェック時のエラーメッセージ。(未設定の場合「XXXX」が必須です。になります) |
|
||||
| rules |"[val=>val<=100 && val>=1 \|\| '1-100の範囲内の数値を入力してください']"| 必須チェック以外のルールを設定する |
|
||||
| fieldTypes |["SINGLE_LINE_TEXT","MULTI_LINE_TEXT","NUMBER"]| FieldInput,AppFieldSelectのみ使用可能。 |
|
||||
|
||||
|
||||
|
||||
### 使用可能なコンポーネント
|
||||
@@ -81,50 +78,14 @@
|
||||
|-----|------------------|------------------|-----------------------------------------|
|
||||
| 1 | テキストボックス | InputText | 一行のテキスト入力が可能なフィールドです。 |
|
||||
| 2 | テキストボックス(改行可能) | MuiltInputText | 複数行のテキスト入力が可能なテキストエリアです。 |
|
||||
| 3 | 数値入力 | NumInput | 数値のみ入力可能フィールド。 |
|
||||
| 4 | 日付 | DatePicker | 日付を選択するためのカレンダーコンポーネントです。 |
|
||||
| 5 | フィールド選択 | FieldInput | システムのフィールドを選択するための入力コンポーネントです。 |
|
||||
| 6 | 選択リスト | SelectBox | 複数のオプションから選択するためのドロップダウンリストです。 |
|
||||
| 7 | 条件式設定 | ConditionInput | 条件式やロジックを入力するためのコンポーネントです。 |
|
||||
| 8 | イベント設定 |EventSetter | ボタンイベント設定のコンポーネントです。 |
|
||||
| 9 | 色選択 | ColorPicker | 色を設定する |
|
||||
| 10 | 他のアプリのフィールド選択 | AppFieldSelect | 他のアプリのフィールドを選択する |
|
||||
| 11 | アプリ選択 | AppSelect | アプリを選択する |
|
||||
|
||||
### フィールド選択コンポーネントのfieldTypes属性を使用可能フィールド種別
|
||||
| 番号 | 項目タイプ名 | 種別タイプ |
|
||||
|------|-----------------------|-------------------|
|
||||
| 1 | カテゴリー | CATEGORY |
|
||||
| 2 | 作成日時 | CREATED_TIME |
|
||||
| 3 | 作成者 | CREATOR |
|
||||
| 4 | 更新者 | MODIFIER |
|
||||
| 5 | レコード番号 | RECORD_NUMBER |
|
||||
| 6 | 更新日時 | UPDATED_TIME |
|
||||
| 7 | 計算 | CALC |
|
||||
| 8 | チェックボックス | CHECK_BOX |
|
||||
| 9 | 日付 | DATE |
|
||||
| 10 | 日時 | DATETIME |
|
||||
| 11 | ドロップダウン | DROP_DOWN |
|
||||
| 12 | 添付ファイル | FILE |
|
||||
| 13 | グループ | GROUP |
|
||||
| 14 | グループ選択 | GROUP_SELECT |
|
||||
| 15 | リンク | LINK |
|
||||
| 16 | 文字列 (複数行) | MULTI_LINE_TEXT |
|
||||
| 17 | 複数選択 | MULTI_SELECT |
|
||||
| 18 | 数値 | NUMBER |
|
||||
| 19 | 組織選択 | ORGANIZATION_SELECT |
|
||||
| 20 | ラジオボタン | RADIO_BUTTON |
|
||||
| 21 | 関連レコード一覧 | REFERENCE_TABLE |
|
||||
| 22 | リッチエディター | RICH_TEXT |
|
||||
| 23 | 文字列 (1行) | SINGLE_LINE_TEXT |
|
||||
| 24 | ステータス | STATUS |
|
||||
| 25 | 作業者 | STATUS_ASSIGNEE |
|
||||
| 26 | テーブル | SUBTABLE |
|
||||
| 27 | 時刻 | TIME |
|
||||
| 28 | ユーザー選択 | USER_SELECT |
|
||||
| 29 | スペース | SPACER |
|
||||
| 30 | ルックアップ | lookup |
|
||||
|
||||
| 3 | 日付 | DatePicker | 日付を選択するためのカレンダーコンポーネントです。 |
|
||||
| 4 | フィールド選択 | FieldInput | システムのフィールドを選択するための入力コンポーネントです。 |
|
||||
| 5 | 選択リスト | SelectBox | 複数のオプションから選択するためのドロップダウンリストです。 |
|
||||
| 6 | 条件式設定 | ConditionInput | 条件式やロジックを入力するためのコンポーネントです。 |
|
||||
| 7 | イベント設定 |EventSetter | ボタンイベント設定のコンポーネントです。 |
|
||||
| 8 | 色選択 | ColorPicker | 色を設定する(追加予定中) |
|
||||
| 9 | 他のアプリのフィールド選択 | AppFieldPicker | 他のアプリのフィールドを選択する(追加予定中) |
|
||||
| 10 |ユーザー選択 | UserPicker | ユーザーを選択する(追加予定中) |
|
||||
|
||||
## 2.アクションアドインの開発
|
||||
|
||||
@@ -309,7 +270,7 @@ npm run build:dev
|
||||
- Azure App Service 拡張機能でデプロイが完了したことを確認します。
|
||||
- ka-addin の URL にアクセスしてアプリケーションが正常に動作しているか確認します。
|
||||
|
||||
3. **ローカルでプラグインをテストする(ZCCの導入ため、廃止する)**
|
||||
3. **ローカルでプラグインをテストする**
|
||||
1. kintone-addinsをPreviewで起動する
|
||||
```bash
|
||||
yarn build:dev
|
||||
@@ -317,7 +278,7 @@ yarn preview
|
||||
#またはyarn devは yarn build:dev + yarn preview と同じです
|
||||
yarn dev
|
||||
```
|
||||
2. **ngrokをインストールする(ZCCの導入ため、廃止する)**
|
||||
2. **ngrokをインストールする**
|
||||
1. [ngrok の公式ウェブサイト](https://ngrok.com/)にアクセスします。
|
||||
2. 「Sign up」をクリックしてアカウントを登録するか、既存のアカウントにログインします。
|
||||
3. 登録またはログイン後、ダッシュボードに進み、ダウンロードリンクが表示されます。操作システム(Windows、macOS、Linux)に応じて、適切なバージョンを選択してダウンロードします。
|
||||
@@ -331,10 +292,4 @@ yarn dev
|
||||
6. ngrok を起動する
|
||||
```bash
|
||||
ngrok https http://localhost:4173/
|
||||
```
|
||||
3. kintone-addinsをビルドする
|
||||
```bash
|
||||
yarn build:dev #開発モード
|
||||
#またはyarn devは yarn build:dev + yarn preview と同じです
|
||||
yarn build #本番リリースモード
|
||||
```
|
||||
```
|
||||
@@ -1,20 +0,0 @@
|
||||
.modal-backdrop {
|
||||
--bs-backdrop-zindex: 1050;
|
||||
--bs-backdrop-bg: #000;
|
||||
--bs-backdrop-opacity: .5;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: var(--bs-backdrop-zindex);
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: var(--bs-backdrop-bg)
|
||||
}
|
||||
|
||||
.modal-backdrop.fade {
|
||||
opacity: 0
|
||||
}
|
||||
|
||||
.modal-backdrop.show {
|
||||
opacity: var(--bs-backdrop-opacity)
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
import {
|
||||
IAction,
|
||||
IActionResult,
|
||||
IActionNode,
|
||||
IActionProperty,
|
||||
IContext,
|
||||
IField,
|
||||
} from "../types/ActionTypes";
|
||||
|
||||
import { actionAddins } from ".";
|
||||
import type { Record} from "@kintone/rest-api-client/lib/src/client/types";
|
||||
import { KintoneAllRecordsError, KintoneRestAPIClient} from "@kintone/rest-api-client";
|
||||
import "./auto-lookup.scss";
|
||||
import "bootstrap/js/dist/modal";
|
||||
// import "bootstrap/js/dist/spinner";
|
||||
import {Modal} from "bootstrap"
|
||||
import $ from "jquery";
|
||||
|
||||
interface IAutoLookUpProps {
|
||||
displayName: string;
|
||||
lookupField: LookupField;
|
||||
condition: Condition;
|
||||
}
|
||||
|
||||
interface Condition {
|
||||
queryString: string;
|
||||
index: number;
|
||||
type: string;
|
||||
children: Child[];
|
||||
parent: null;
|
||||
logicalOperator: string;
|
||||
}
|
||||
|
||||
interface Child {
|
||||
index: number;
|
||||
type: string;
|
||||
parent: string;
|
||||
object: any;
|
||||
operator: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface LookupField {
|
||||
app: App;
|
||||
fields: Field[];
|
||||
}
|
||||
|
||||
interface Field {
|
||||
name: string;
|
||||
type: string;
|
||||
code: string;
|
||||
label: string;
|
||||
noLabel: boolean;
|
||||
required: boolean;
|
||||
lookup: Lookup;
|
||||
}
|
||||
|
||||
interface Lookup {
|
||||
relatedApp: RelatedApp;
|
||||
relatedKeyField: string;
|
||||
fieldMappings: FieldMapping[];
|
||||
lookupPickerFields: any[];
|
||||
filterCond: string;
|
||||
sort: string;
|
||||
}
|
||||
|
||||
interface FieldMapping {
|
||||
field: string;
|
||||
relatedField: string;
|
||||
}
|
||||
|
||||
interface RelatedApp {
|
||||
app: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
interface App {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdate: string;
|
||||
}
|
||||
|
||||
export class AutoLookUpAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
props: IAutoLookUpProps;
|
||||
constructor() {
|
||||
this.name = "ルックアップ更新";
|
||||
this.actionProps = [];
|
||||
this.props = {} as IAutoLookUpProps;
|
||||
this.register();
|
||||
}
|
||||
|
||||
/***
|
||||
* アクセスのメインの処理関数
|
||||
*/
|
||||
async process(
|
||||
actionNode: IActionNode,
|
||||
event: any,
|
||||
context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.actionProps = actionNode.actionProps;
|
||||
this.props = {
|
||||
...actionNode.ActionValue,
|
||||
condition: JSON.parse((actionNode.ActionValue as any).condition),
|
||||
} as IAutoLookUpProps;
|
||||
// console.log(context);
|
||||
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: "",
|
||||
} as IActionResult;
|
||||
try {
|
||||
const lookUpFields = this.props.lookupField.fields.filter(
|
||||
(f) => f.lookup && f.lookup.relatedApp.app === String(kintone.app.getId())
|
||||
);
|
||||
if (!lookUpFields || lookUpFields.length===0) {
|
||||
throw new Error(
|
||||
`ルックアップの設定は不正です。${this.props.lookupField.fields[0].label} `
|
||||
);
|
||||
}
|
||||
const lookUpField = this.props.lookupField.fields[0];
|
||||
const key = event.record[lookUpField.lookup.relatedKeyField].value;
|
||||
const targetRecords = await this.getUpdateRecords(lookUpField, key);
|
||||
//更新対象がない時にスキップ
|
||||
if(targetRecords.length===0){
|
||||
return result;
|
||||
}
|
||||
const updateRecords = this.convertForLookup(targetRecords,lookUpField,key);
|
||||
console.log("updateRecords", updateRecords);
|
||||
this.showSpinnerModel(this.props.lookupField.app,lookUpField);
|
||||
const updateResult = await this.updateLookupTarget(updateRecords);
|
||||
if(updateResult){
|
||||
this.showResult(this.props.lookupField.app,lookUpField,updateRecords.length);
|
||||
}
|
||||
} catch (error) {
|
||||
this.closeDialog();
|
||||
context.errors.handleError(error,actionNode,"ルックアップ更新中例外が発生しました");
|
||||
result.canNext = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* REST API用クエリ作成
|
||||
* TODO:共通関数として作成
|
||||
* @param lookUpField
|
||||
* @param key
|
||||
* @returns
|
||||
*/
|
||||
makeQuery=(lookUpField:Field,key:any)=>{
|
||||
let query ="";
|
||||
if(typeof key==='number'){
|
||||
query = `${lookUpField.code} = ${key}`
|
||||
}
|
||||
if(typeof key==='string'){
|
||||
query = `${lookUpField.code} = "${key}"`
|
||||
}
|
||||
if(this.props.condition.queryString!==''){
|
||||
query = `${query} and (${this.props.condition.queryString})`
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新対象のレコードを取得する
|
||||
*/
|
||||
getUpdateRecords = async (lookUpField:Field,key:any):Promise< Record[]>=>{
|
||||
const client=new KintoneRestAPIClient();
|
||||
const resp = await client.record.getAllRecords({
|
||||
app:this.props.lookupField.app.id,
|
||||
fields:["$id"],
|
||||
condition:this.makeQuery(lookUpField,key)
|
||||
});
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* ルックアップ更新用レコードに変換する
|
||||
* @param targetRecords 更新対象レコード
|
||||
* @param lookUpField ルックアップフィールド
|
||||
* @param key ルックアップフィールドの値
|
||||
* @returns
|
||||
*/
|
||||
convertForLookup = (targetRecords:Record[],lookUpField:Field,key:any):Array<any>=>{
|
||||
return targetRecords.map((r) => ({
|
||||
id: Number(r["$id"].value),
|
||||
record: { [lookUpField.code]: { value: key } },
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* ルックアップ先を更新する
|
||||
* @param updateRecords
|
||||
*/
|
||||
updateLookupTarget = async (updateRecords:Array<any>):Promise<boolean>=>{
|
||||
if (updateRecords && updateRecords.length > 0) {
|
||||
try{
|
||||
const client=new KintoneRestAPIClient();
|
||||
const result = await client.record.updateAllRecords({
|
||||
app:this.props.lookupField.app.id,
|
||||
records:updateRecords
|
||||
});
|
||||
return true;
|
||||
}catch(error ){
|
||||
if(error instanceof KintoneAllRecordsError){
|
||||
this.showError(this.props.lookupField.app,
|
||||
this.props.lookupField.fields[0],
|
||||
error as KintoneAllRecordsError,updateRecords.length);
|
||||
return false;
|
||||
}else{
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// await kintone.api(kintone.api.url("/k/v1/records.json", true), "PUT", {
|
||||
// app: this.props.lookupField.app.id,
|
||||
// records: updateRecords
|
||||
// });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新中のダイアログ表示
|
||||
* @param app
|
||||
*/
|
||||
showSpinnerModel = (app:App,lookup:Field) => {
|
||||
let dialog = $("#alcLookupModal");
|
||||
if(dialog.length===0){
|
||||
const modalHTML = `<div class="bs-scope">
|
||||
<div class="modal" id="alcLookupModal" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
|
||||
<div class="modal-dialog-centered">
|
||||
<div class="modal-dialog modal-content">
|
||||
<div class="modal-header">
|
||||
<h1 class="modal-title fs-5" id="alcLookupModalLabel">ルックアップ同期処理</h1>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="row" id="app${app.id}_${lookup.code}">
|
||||
<div class="spinner-border text-secondary col-1 " role="alert"></div>
|
||||
<div class="col">${app.name}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
|
||||
</div>
|
||||
</div></div></div></div>`;
|
||||
$(modalHTML).appendTo("body");
|
||||
dialog = $("#alcLookupModal");
|
||||
dialog.get()[0].addEventListener('hidden.bs.modal',(ev)=>{
|
||||
Modal.getOrCreateInstance(dialog.get()[0]).dispose();
|
||||
$("#alcLookupModal").parent().remove();
|
||||
});
|
||||
}else{
|
||||
const dialogBody=$("#alcLookupModal .modal-body");
|
||||
const htmlrow=`
|
||||
<div class="row" id="app${app.id}_${lookup.code}">
|
||||
<div class="spinner-border text-secondary col-1 " role="alert">
|
||||
</div>
|
||||
<div class="col">${app.name}</div>
|
||||
<div>`;
|
||||
dialogBody.append(htmlrow);
|
||||
}
|
||||
Modal.getOrCreateInstance(dialog.get()[0]).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新結果を表示する
|
||||
* @param app 更新先アプリ情報
|
||||
* @param count 更新件数
|
||||
*/
|
||||
showResult=(app:App,lookup:Field,count:number)=>{
|
||||
const dialogBody=$(`#alcLookupModal .modal-body #app${app.id}_${lookup.code}`);
|
||||
const html=` <div class="col-1 text-success">✔</div>
|
||||
<div class="col">${app.name}</div>
|
||||
<div class="col">更新件数:${count}件</div>`;
|
||||
dialogBody.html(html);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新結果を表示する
|
||||
* @param app 更新先アプリ情報
|
||||
* @param count 更新件数
|
||||
*/
|
||||
showError=(app:App,lookup:Field,error:KintoneAllRecordsError,allCount:Number)=>{
|
||||
const message=error.error.message;
|
||||
const proRecords = error.numOfProcessedRecords;
|
||||
const allRecords=error.numOfAllRecords;
|
||||
const dialogBody=$(`#alcLookupModal .modal-body #app${app.id}_${lookup.code}`);
|
||||
const html=`<div class="col-1 text-danger">✖</div>
|
||||
<div class="col">${app.name}</div>
|
||||
<div class="col">更新件数:${proRecords}/${allRecords}</div>
|
||||
<div class="row text-danger">${message}<div>`;
|
||||
dialogBody.html(html);
|
||||
}
|
||||
/**
|
||||
* ダイアログ画面を閉じる
|
||||
*/
|
||||
closeDialog=()=>{
|
||||
const dialog = $("#alcLookupModal");
|
||||
Modal.getOrCreateInstance(dialog.get()[0]).dispose();
|
||||
$("#alcLookupModal").parent().remove();
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
new AutoLookUpAction();
|
||||
24
plugin/kintone-addins/src/actions/auto-numbering.css
Normal file
24
plugin/kintone-addins/src/actions/auto-numbering.css
Normal file
@@ -0,0 +1,24 @@
|
||||
.alc-button-normal {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
padding: 0 16px;
|
||||
margin-left: 16px;
|
||||
margin-top: 8px;
|
||||
min-width: 100px;
|
||||
outline: none;
|
||||
border: 1px solid #e3e7e8;
|
||||
background-color: #f7f9fa;
|
||||
box-shadow: 1px 1px 1px #fff inset;
|
||||
color: #3498db;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
}
|
||||
.alc-button-normal:hover {
|
||||
background-color: #c8d6dd;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.alc-button-normal:active {
|
||||
color: #f7f9fa;
|
||||
background-color: #54b8eb;
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
|
||||
import { actionAddins } from ".";
|
||||
import { IField, IAction,IActionResult, IActionNode, IActionProperty, IContext, IVarName } from "../types/ActionTypes";
|
||||
import { IField, IAction,IActionResult, IActionNode, IActionProperty, IContext } from "../types/ActionTypes";
|
||||
import { Formatter } from "../util/format";
|
||||
import "./auto-numbering.css";
|
||||
|
||||
declare global {
|
||||
interface Window { $format: any; }
|
||||
@@ -13,7 +14,7 @@ interface IAutoNumberingProps{
|
||||
format:string;
|
||||
prefix:string;
|
||||
suffix:string;
|
||||
verName:IVarName;
|
||||
verName:string;
|
||||
}
|
||||
|
||||
export class AutoNumbering implements IAction{
|
||||
@@ -29,7 +30,7 @@ export class AutoNumbering implements IAction{
|
||||
format:'',
|
||||
prefix:'',
|
||||
suffix:'',
|
||||
verName:{name:''}
|
||||
verName:''
|
||||
}
|
||||
globalThis.window.$format=this.format;
|
||||
this.register();
|
||||
@@ -56,8 +57,8 @@ export class AutoNumbering implements IAction{
|
||||
const docNum = await this.createNumber(this.props);
|
||||
record[this.props.field.code].value=docNum;
|
||||
//変数設定
|
||||
if(this.props.verName && this.props.verName.name!==''){
|
||||
context.variables[this.props.verName.name]=docNum;
|
||||
if(this.props.verName){
|
||||
context.variables[this.props.verName]=docNum;
|
||||
}
|
||||
result= {
|
||||
canNext:true,
|
||||
@@ -65,7 +66,8 @@ export class AutoNumbering implements IAction{
|
||||
}
|
||||
return result;
|
||||
}catch(error){
|
||||
context.errors.handleError(error,actionNode);
|
||||
console.error(error);
|
||||
event.error="処理中異常が発生しました。";
|
||||
return {
|
||||
canNext:false,
|
||||
result:false
|
||||
|
||||
47
plugin/kintone-addins/src/actions/bootstrap.scss
vendored
47
plugin/kintone-addins/src/actions/bootstrap.scss
vendored
@@ -1,47 +0,0 @@
|
||||
// @import 'bootstrap/scss/bootstrap';
|
||||
@import "bootstrap/scss/functions";
|
||||
@import "bootstrap/scss/variables";
|
||||
@import "bootstrap/scss/variables-dark";
|
||||
@import "bootstrap/scss/maps";
|
||||
@import "bootstrap/scss/mixins";
|
||||
@import "bootstrap/scss/root";
|
||||
.bs-scope{
|
||||
// Required
|
||||
@import "bootstrap/scss/utilities";
|
||||
@import "bootstrap/scss/reboot";
|
||||
@import "bootstrap/scss/type";
|
||||
@import "bootstrap/scss/images";
|
||||
@import "bootstrap/scss/containers";
|
||||
@import "bootstrap/scss/grid";
|
||||
// @import "bootstrap/scss/tables";
|
||||
@import "bootstrap/scss/forms";
|
||||
@import "bootstrap/scss/buttons";
|
||||
@import "bootstrap/scss/transitions";
|
||||
@import "bootstrap/scss/dropdown";
|
||||
// @import "bootstrap/scss/button-group";
|
||||
// @import "bootstrap/scss/nav";
|
||||
// @import "bootstrap/scss/navbar"; // Requires nav
|
||||
@import "bootstrap/scss/card";
|
||||
// @import "bootstrap/scss/breadcrumb";
|
||||
// @import "bootstrap/scss/accordion";
|
||||
// @import "bootstrap/scss/pagination";
|
||||
// @import "bootstrap/scss/badge";
|
||||
// @import "bootstrap/scss/alert";
|
||||
// @import "bootstrap/scss/progress";
|
||||
// @import "bootstrap/scss/list-group";
|
||||
@import "bootstrap/scss/close";
|
||||
// @import "bootstrap/scss/toasts";
|
||||
@import "bootstrap/scss/modal"; // Requires transitions
|
||||
// @import "bootstrap/scss/tooltip";
|
||||
@import "bootstrap/scss/popover";
|
||||
// @import "bootstrap/scss/carousel";
|
||||
@import "bootstrap/scss/spinners";
|
||||
@import "bootstrap/scss/offcanvas"; // Requires transitions
|
||||
// @import "bootstrap/scss/placeholders";
|
||||
|
||||
// Helpers
|
||||
// @import "bootstrap/scss/helpers";
|
||||
|
||||
// Utilities
|
||||
@import "bootstrap/scss/utilities/api";
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
.alc-button-normal {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
padding: 0 16px;
|
||||
margin-left: 16px;
|
||||
margin-top: 8px;
|
||||
min-width: 100px;
|
||||
outline: none;
|
||||
border: 1px solid #e3e7e8;
|
||||
background-color: #f7f9fa;
|
||||
box-shadow: 1px 1px 1px #fff inset;
|
||||
color: #3498db;
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
}
|
||||
.alc-button-normal:hover {
|
||||
background-color: #c8d6dd;
|
||||
box-shadow: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
.alc-button-normal:active {
|
||||
color: #f7f9fa;
|
||||
background-color: #54b8eb;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import { actionAddins } from ".";
|
||||
import $ from 'jquery';
|
||||
import { IAction, IActionProperty, IActionNode, IActionResult, IContext } from "../types/ActionTypes";
|
||||
import { IAction, IActionProperty, IActionNode, IActionResult } from "../types/ActionTypes";
|
||||
import "./button-add.css";
|
||||
|
||||
/**
|
||||
@@ -10,18 +10,12 @@ import "./button-add.css";
|
||||
interface IButtonAddProps {
|
||||
//ボタン表示名
|
||||
buttonName: string;
|
||||
space?:ISpace;
|
||||
//配置位置
|
||||
position: string;
|
||||
//イベント名
|
||||
eventName:string
|
||||
}
|
||||
|
||||
interface ISpace{
|
||||
type:string,
|
||||
elementId:string
|
||||
}
|
||||
|
||||
export class ButtonAddAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
@@ -43,7 +37,7 @@ export class ButtonAddAction implements IAction {
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
async process(actionNode: IActionNode, event: any,context:IContext): Promise<IActionResult> {
|
||||
async process(actionNode: IActionNode, event: any): Promise<IActionResult> {
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: false
|
||||
@@ -55,19 +49,20 @@ export class ButtonAddAction implements IAction {
|
||||
}
|
||||
this.props = actionNode.ActionValue as IButtonAddProps;
|
||||
//ボタンを配置する
|
||||
let buttonSpace;
|
||||
if(this.props.space && this.props.space.elementId){
|
||||
buttonSpace = kintone.app.record.getSpaceElement(this.props.space.elementId);
|
||||
}else{
|
||||
buttonSpace = kintone.app.record.getHeaderMenuSpaceElement();
|
||||
const menuSpace = kintone.app.record.getHeaderMenuSpaceElement();
|
||||
if(!menuSpace) return result;
|
||||
if($("style#alc-button-add").length===0){
|
||||
const css=`
|
||||
`;
|
||||
const style = $("<style id='alc-button-add'>/<style>");
|
||||
style.text(css);
|
||||
$("head").append(style);
|
||||
}
|
||||
if(!buttonSpace) return result;
|
||||
|
||||
const button =$(`<button id='${this.props.eventName}' class='alc-button-normal' >${this.props.buttonName}</button>`);
|
||||
if(this.props.position==="一番左に追加する"){
|
||||
$(buttonSpace).prepend(button);
|
||||
$(menuSpace).prepend(button);
|
||||
}else{
|
||||
$(buttonSpace).append(button);
|
||||
$(menuSpace).append(button);
|
||||
}
|
||||
const clickEventName = `${event.type}.customButtonClick.${this.props.eventName}`;
|
||||
button.on("click",()=>{
|
||||
@@ -75,7 +70,8 @@ export class ButtonAddAction implements IAction {
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
context.errors.handleError(error,actionNode);
|
||||
event.error = error;
|
||||
console.error(error);
|
||||
result.canNext = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
.alc-loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, .5);
|
||||
display: flex;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.alc-loading > div {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.alc-dnone{
|
||||
display: none;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
import {
|
||||
IAction,
|
||||
IActionResult,
|
||||
IActionNode,
|
||||
IActionProperty,
|
||||
IContext,
|
||||
} from "../types/ActionTypes";
|
||||
import { actionAddins } from ".";
|
||||
// import { KintoneRestAPIClient } from "@kintone/rest-api-client";
|
||||
// import { getPageState } from "../util/url";
|
||||
import { Snipper } from '../util/ui-helper';
|
||||
import { DropDownManager,ICascadingDropDown, IFieldList} from '../types/CascadingDropDownManager'
|
||||
import "./cascading-dropdown.scss";
|
||||
// import { Record as KTRecord } from "@kintone/rest-api-client/lib/src/client/types";
|
||||
|
||||
// 階層化ドロップダウンメニューのプロパティインターフェース
|
||||
interface ICascadingDropDownProps {
|
||||
displayName: string;
|
||||
cascadingDropDown: ICascadingDropDown;
|
||||
}
|
||||
|
||||
/**
|
||||
* 階層化ドロップダウンのクラス実装
|
||||
*/
|
||||
export class CascadingDropDownAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
props: ICascadingDropDownProps;
|
||||
|
||||
constructor() {
|
||||
this.name = "階層化ドロップダウン";
|
||||
this.actionProps = [];
|
||||
this.props = {} as ICascadingDropDownProps;
|
||||
this.register();
|
||||
}
|
||||
|
||||
/**
|
||||
* アクションのプロセス実行
|
||||
* @param actionNode
|
||||
* @param event
|
||||
* @param context
|
||||
* @returns
|
||||
*/
|
||||
async process(
|
||||
actionNode: IActionNode,
|
||||
event: any,
|
||||
context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.actionProps = actionNode.actionProps;
|
||||
this.props = actionNode.ActionValue as ICascadingDropDownProps;
|
||||
|
||||
const result: IActionResult = { canNext: true, result: "" };
|
||||
const snipper = new Snipper("body");
|
||||
const dropDownManager= new DropDownManager(this.props.cascadingDropDown,event);
|
||||
try {
|
||||
if (!this.props) return result;
|
||||
const appId = this.props.cascadingDropDown.dropDownApp.id;
|
||||
//snipper表示
|
||||
snipper.showSpinner();
|
||||
await dropDownManager.handlePageState(appId);
|
||||
snipper.hideSpinner();
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"CascadingDropDownAction プロセス中にエラーが発生しました:",
|
||||
error
|
||||
);
|
||||
context.errors.handleError(error, actionNode);
|
||||
return { canNext: false, result: "" };
|
||||
}finally{
|
||||
snipper.removeSpinner();
|
||||
}
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
new CascadingDropDownAction();
|
||||
@@ -1,13 +1,13 @@
|
||||
|
||||
import { actionAddins } from ".";
|
||||
import { IAction,IActionResult, IActionNode, IActionProperty, IContext, IVarName } from "../types/ActionTypes";
|
||||
import { IAction,IActionResult, IActionNode, IActionProperty, IContext } from "../types/ActionTypes";
|
||||
import { ConditionTree } from '../types/Conditions';
|
||||
/**
|
||||
* アクションの属性定義
|
||||
*/
|
||||
interface ICondition{
|
||||
condition:string;
|
||||
verName:IVarName;
|
||||
verName:string;
|
||||
}
|
||||
/**
|
||||
* 条件分岐アクション
|
||||
@@ -21,7 +21,7 @@ export class ConditionAction implements IAction{
|
||||
this.actionProps=[];
|
||||
this.props={
|
||||
condition:'',
|
||||
verName:{name:''}
|
||||
verName:''
|
||||
}
|
||||
//アクションを登録する
|
||||
this.register();
|
||||
@@ -58,12 +58,13 @@ export class ConditionAction implements IAction{
|
||||
result:'いいえ'
|
||||
}
|
||||
}
|
||||
if(this.props.verName && this.props.verName.name!==''){
|
||||
context.variables[this.props.verName.name]=result.result;
|
||||
if(this.props.verName){
|
||||
context.variables[this.props.verName]=result.result;
|
||||
}
|
||||
return result;
|
||||
}catch(error){
|
||||
context.errors.handleError(error,actionNode);
|
||||
event.error=error;
|
||||
console.error(error);
|
||||
result.canNext=false;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import { actionAddins } from ".";
|
||||
import { IAction,IActionResult, IActionNode, IActionProperty, IField, IContext} from "../types/ActionTypes";
|
||||
/**
|
||||
* アクションの属性定義
|
||||
*/
|
||||
interface IStrCountCheckProps{
|
||||
field:IField;//チェックするフィールドの対象
|
||||
message:string;//エラーメッセージ
|
||||
maxLength:number;//
|
||||
}
|
||||
/**
|
||||
* 正規表現チェックアクション
|
||||
*/
|
||||
export class StrCountCheckAciton implements IAction{
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
props:IStrCountCheckProps;
|
||||
constructor(){
|
||||
this.name="文字数チェック";
|
||||
this.actionProps=[];
|
||||
this.props={
|
||||
field:{code:''},
|
||||
message:'',
|
||||
maxLength:0
|
||||
}
|
||||
//アクションを登録する
|
||||
this.register();
|
||||
}
|
||||
/**
|
||||
* アクションの実行を呼び出す
|
||||
* @param actionNode
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
async process(actionNode:IActionNode,event:any,context:IContext):Promise<IActionResult> {
|
||||
let result={
|
||||
canNext:true,
|
||||
result:false
|
||||
};
|
||||
try{
|
||||
//属性設定を取得する
|
||||
this.actionProps=actionNode.actionProps;
|
||||
if (!('field' in actionNode.ActionValue) && !('message' in actionNode.ActionValue) && !('strExpression'in actionNode.ActionValue)) {
|
||||
return result
|
||||
}
|
||||
|
||||
this.props = actionNode.ActionValue as IStrCountCheckProps;
|
||||
//条件式の計算結果を取得
|
||||
const record = event.record;
|
||||
const value = record[this.props.field.code].value;
|
||||
const maxLength = this.props.maxLength;
|
||||
if(value === undefined || value === '' ){
|
||||
return result;
|
||||
}else if(maxLength < value.length){
|
||||
record[this.props.field.code].error = this.props.message;
|
||||
}else{
|
||||
record[this.props.field.code].error = null;
|
||||
}
|
||||
result= {
|
||||
canNext:true,
|
||||
result:true
|
||||
}
|
||||
return result;
|
||||
|
||||
}catch(error){
|
||||
context.errors.handleError(error,actionNode);
|
||||
result.canNext=false;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
register(): void {
|
||||
actionAddins[this.name]=this;
|
||||
}
|
||||
|
||||
}
|
||||
new StrCountCheckAciton();
|
||||
@@ -1,69 +0,0 @@
|
||||
import {
|
||||
IAction,
|
||||
IActionResult,
|
||||
IActionNode,
|
||||
IActionProperty,
|
||||
IContext,
|
||||
IField,
|
||||
} from "../types/ActionTypes";
|
||||
import { actionAddins } from ".";
|
||||
|
||||
|
||||
interface ICurrentFieldGetProps {
|
||||
displayName: string;
|
||||
field: IField;
|
||||
verName: VerName;
|
||||
}
|
||||
|
||||
interface VerName {
|
||||
name: string;
|
||||
}
|
||||
|
||||
|
||||
export class CurrentFieldGetAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
props: ICurrentFieldGetProps;
|
||||
constructor() {
|
||||
this.name = "フィールドの値を取得する";
|
||||
this.actionProps = [];
|
||||
this.props = {} as ICurrentFieldGetProps;
|
||||
this.register();
|
||||
}
|
||||
|
||||
async process(
|
||||
actionNode: IActionNode,
|
||||
event: any,
|
||||
context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.props = actionNode.ActionValue as ICurrentFieldGetProps;
|
||||
this.actionProps = actionNode.actionProps;
|
||||
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: '',
|
||||
} as IActionResult;
|
||||
|
||||
try {
|
||||
const record = event.record;
|
||||
if(!(this.props.field.code in record)){
|
||||
throw new Error(`フィールド[${this.props.field.code}]が見つかりませんでした。`);
|
||||
}
|
||||
//変数設定
|
||||
if(this.props.verName && this.props.verName.name!==''){
|
||||
context.variables[this.props.verName.name]=record[this.props.field.code].value;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
context.errors.handleError(error,actionNode);
|
||||
result.canNext = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
new CurrentFieldGetAction();
|
||||
182
plugin/kintone-addins/src/actions/data-mapping.ts
Normal file
182
plugin/kintone-addins/src/actions/data-mapping.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import {
|
||||
IAction,
|
||||
IActionResult,
|
||||
IActionNode,
|
||||
IActionProperty,
|
||||
IContext,
|
||||
} from "../types/ActionTypes";
|
||||
import { actionAddins } from ".";
|
||||
|
||||
export type IApp = {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
export type IField = {
|
||||
name: string;
|
||||
code: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type IAppFields = {
|
||||
app?: IApp;
|
||||
fields: IField[];
|
||||
};
|
||||
|
||||
type ValueType = {
|
||||
id: string;
|
||||
from: {
|
||||
objectType: "variable" | "field";
|
||||
name: { name: string };
|
||||
code: string;
|
||||
};
|
||||
to: IAppFields & {
|
||||
isDialogVisible: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
type Props = { app: IApp; field: ValueType[] };
|
||||
|
||||
export class DataMappingAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
dataMappingProps: Props;
|
||||
constructor() {
|
||||
this.name = "DataMapping";
|
||||
this.actionProps = [];
|
||||
this.dataMappingProps = {} as Props;
|
||||
this.register();
|
||||
}
|
||||
|
||||
async process(
|
||||
prop: IActionNode,
|
||||
event: any,
|
||||
context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.initActionProps(prop);
|
||||
this.initTypedActionProps();
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: "",
|
||||
} as IActionResult;
|
||||
try {
|
||||
for (const item of this.dataMappingProps.field) {
|
||||
if (item.from.objectType === "variable") {
|
||||
if (
|
||||
item.from.name.name &&
|
||||
item.to.app &&
|
||||
item.to.fields &&
|
||||
item.to.fields.length > 0
|
||||
) {
|
||||
const value = getValueByPath(
|
||||
context.variables,
|
||||
item.from.name.name
|
||||
);
|
||||
if (value) {
|
||||
await kintone.api(
|
||||
kintone.api.url("/k/v1/record.json", true),
|
||||
"POST",
|
||||
{
|
||||
app: item.to.app.id,
|
||||
record: {
|
||||
[item.to.fields[0].code]: {
|
||||
value: value,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (item.from.objectType === "field") {
|
||||
if (
|
||||
item.from.code &&
|
||||
item.to.app &&
|
||||
item.to.fields &&
|
||||
item.to.fields.length > 0
|
||||
) {
|
||||
const value = await selectData(
|
||||
item.to.app.id,
|
||||
item.to.fields[0].code
|
||||
);
|
||||
if (value && value.type === context.record[item.from.code].type) {
|
||||
await kintone.api(
|
||||
kintone.api.url("/k/v1/records.json", true),
|
||||
"POST",
|
||||
{
|
||||
app: item.to.app.id,
|
||||
records: value.value.map((v) => ({
|
||||
[item.to.fields[0].code]: {
|
||||
value: v,
|
||||
},
|
||||
})),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("DataMappingAction error", error);
|
||||
result.canNext = false;
|
||||
}
|
||||
console.log("dataMappingProps", this.dataMappingProps);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private initActionProps(nodes: IActionNode) {
|
||||
this.actionProps = nodes.actionProps;
|
||||
}
|
||||
private initTypedActionProps() {
|
||||
for (const action of this.actionProps) {
|
||||
if (action.component === "DataMapping") {
|
||||
this.dataMappingProps.field = action.props.modelValue as ValueType[];
|
||||
} else if (action.component === "AppSelect") {
|
||||
this.dataMappingProps.app = action.props.modelValue.app as IApp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
new DataMappingAction();
|
||||
|
||||
const getValueByPath = (obj: any, path: string) => {
|
||||
return path.split(".").reduce((o, k) => (o || {})[k], obj);
|
||||
};
|
||||
|
||||
type Resp = { records: RespRecordType[] };
|
||||
|
||||
type RespRecordType = {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
value: any;
|
||||
};
|
||||
};
|
||||
|
||||
type Result = {
|
||||
type: string;
|
||||
value: any[];
|
||||
};
|
||||
|
||||
const selectData = async (appid: string, field: string): Promise<Result> => {
|
||||
return kintone
|
||||
.api(kintone.api.url("/k/v1/records", true), "GET", {
|
||||
app: appid ?? kintone.app.getId(),
|
||||
fields: [field],
|
||||
})
|
||||
.then((resp: Resp) => {
|
||||
const result: Result = { type: "", value: [] };
|
||||
resp.records.forEach((element) => {
|
||||
for (const [key, value] of Object.entries(element)) {
|
||||
if (result.type === "") {
|
||||
result.type = value.type;
|
||||
}
|
||||
result.value.push(value.value);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
});
|
||||
};
|
||||
@@ -6,161 +6,130 @@ import {
|
||||
IContext,
|
||||
} from "../types/ActionTypes";
|
||||
import { actionAddins } from ".";
|
||||
import {KintoneRestAPIClient} from '@kintone/rest-api-client';
|
||||
import { Aggregator,Operator} from '../util/aggregates';
|
||||
import { FieldForm } from "../types/FieldLayout";
|
||||
|
||||
interface IDataProcessingProps {
|
||||
displayName: string;
|
||||
sources: Sources;
|
||||
condition: string;
|
||||
verName?: VerName;
|
||||
}
|
||||
|
||||
interface Condition {
|
||||
queryString: string;
|
||||
}
|
||||
|
||||
interface VerName {
|
||||
name: string;
|
||||
actionName: string;
|
||||
displayName: string;
|
||||
vars: Var[];
|
||||
}
|
||||
|
||||
interface Var {
|
||||
id: string;
|
||||
field: FieldForm;
|
||||
logicalOperator: CalcOperator;
|
||||
vName: string;
|
||||
}
|
||||
|
||||
interface CalcOperator {
|
||||
operator: Operator;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface Sources {
|
||||
app: App;
|
||||
fields: FieldForm[];
|
||||
}
|
||||
|
||||
interface App {
|
||||
id: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
type Result = {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
value: any[];
|
||||
type DataProcessingProps = {
|
||||
app: {
|
||||
id: string;
|
||||
name: string;
|
||||
};
|
||||
conditionsQuery: string;
|
||||
propcessing: {
|
||||
varRootName: string;
|
||||
fields: Field[];
|
||||
};
|
||||
};
|
||||
|
||||
type AnyObject = {
|
||||
[key: string]: any;
|
||||
type Field = {
|
||||
name: string;
|
||||
code: string;
|
||||
type: string;
|
||||
varName: string;
|
||||
operator: string;
|
||||
};
|
||||
|
||||
export class DataProcessingAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
props: IDataProcessingProps ;
|
||||
|
||||
dataProcessingProps: DataProcessingProps | null;
|
||||
constructor() {
|
||||
this.name = "データ処理";
|
||||
this.actionProps = [];
|
||||
this.props = {
|
||||
displayName:'',
|
||||
condition:'',
|
||||
sources:{
|
||||
app:{
|
||||
id:""
|
||||
},
|
||||
fields:[]
|
||||
},
|
||||
};
|
||||
this.dataProcessingProps = null;
|
||||
this.register();
|
||||
}
|
||||
|
||||
async process(
|
||||
actionNode: IActionNode,
|
||||
event: any,
|
||||
context: IContext
|
||||
nodes: IActionNode,event: any,context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.actionProps = actionNode.actionProps;
|
||||
|
||||
this.props = actionNode.ActionValue as IDataProcessingProps;
|
||||
const condition = JSON.parse(this.props.condition) as Condition;
|
||||
this.initActionProps(nodes);
|
||||
this.initTypedActionProps();
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: "",
|
||||
} as IActionResult;
|
||||
try {
|
||||
if (!this.props) {
|
||||
if (!this.dataProcessingProps) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const query = this.getQuery(condition.queryString,context.variables);
|
||||
const data = await this.selectData(query);
|
||||
|
||||
|
||||
const data = await selectData(this.dataProcessingProps.conditionsQuery);
|
||||
console.log("data ", data);
|
||||
|
||||
if(this.props.verName){
|
||||
const varValues= this.props.verName.vars.reduce((acc, f) => {
|
||||
const datas=data[f.field.code].value;
|
||||
const agg = new Aggregator(datas,f.field);
|
||||
const result = agg.calculate(f.logicalOperator.operator)
|
||||
acc[f.vName]=result;
|
||||
return acc;
|
||||
}, {} as AnyObject);
|
||||
context.variables[this.props.verName.name]=varValues;
|
||||
console.log("context ", context);
|
||||
}
|
||||
context.variables[this.dataProcessingProps.propcessing.varRootName] =
|
||||
this.dataProcessingProps.propcessing.fields.reduce((acc, f) => {
|
||||
const v = calc(f, data);
|
||||
if (v) {
|
||||
acc[f.varName] = calc(f, data);
|
||||
}
|
||||
return acc;
|
||||
}, {} as Var);
|
||||
|
||||
console.log("context ", context);
|
||||
return result;
|
||||
} catch (error) {
|
||||
context.errors.handleError(error,actionNode);
|
||||
result.canNext = false;
|
||||
console.error(error);
|
||||
event.error=error;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param str
|
||||
* @param vars
|
||||
* @returns
|
||||
*/
|
||||
getQuery = (str: string, vars: any) => {
|
||||
console.log(str);
|
||||
const regex = /var\((.*?)\)/g;
|
||||
let match;
|
||||
while ((match = regex.exec(str)) !== null) {
|
||||
const varName = match[1];
|
||||
if (varName in vars) {
|
||||
str = str.replace(match[0], vars[varName]);
|
||||
} else {
|
||||
throw new Error(`変数${varName}が見つかりません`);
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
|
||||
private initActionProps(nodes: IActionNode) {
|
||||
this.actionProps = nodes.actionProps;
|
||||
}
|
||||
|
||||
private initTypedActionProps() {
|
||||
this.dataProcessingProps = {
|
||||
app: {
|
||||
id: "",
|
||||
name: "",
|
||||
},
|
||||
conditionsQuery: "",
|
||||
propcessing: {
|
||||
varRootName: "",
|
||||
fields: [],
|
||||
},
|
||||
};
|
||||
for (const action of this.actionProps) {
|
||||
if (action.component === "AppFieldSelect") {
|
||||
this.dataProcessingProps.app.id = action.props.modelValue.app.id;
|
||||
this.dataProcessingProps.app.name = action.props.modelValue.app.name;
|
||||
} else if (action.component === "DataProcessing") {
|
||||
this.dataProcessingProps.propcessing.varRootName =
|
||||
action.props.modelValue.name;
|
||||
for (const f of action.props.modelValue.vars) {
|
||||
this.dataProcessingProps.propcessing.fields.push({
|
||||
name: f.field.name,
|
||||
code: f.field.code,
|
||||
type: f.field.type,
|
||||
varName: f.vName,
|
||||
operator: f.logicalOperator.operator,
|
||||
});
|
||||
}
|
||||
} else if (action.component === "ConditionInput") {
|
||||
this.dataProcessingProps.conditionsQuery = JSON.parse(
|
||||
action.props.modelValue
|
||||
).queryString;
|
||||
}
|
||||
}
|
||||
console.log(str);
|
||||
return str;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* データを取得する
|
||||
* @param query
|
||||
* @returns
|
||||
*/
|
||||
selectData = async ( query?: string) => {
|
||||
const api = new KintoneRestAPIClient();
|
||||
const fields = this.props.sources.fields.map((field)=>field.code);
|
||||
const resp = await api.record.getAllRecords({
|
||||
app: this.props.sources.app.id,
|
||||
fields:fields,
|
||||
condition:query
|
||||
});
|
||||
new DataProcessingAction();
|
||||
|
||||
const selectData = async (query?: string) => {
|
||||
return kintone
|
||||
.api(kintone.api.url("/k/v1/records", true), "GET", {
|
||||
app: kintone.app.getId(),
|
||||
query: query,
|
||||
})
|
||||
.then((resp: Resp) => {
|
||||
const result: Result = {};
|
||||
resp.forEach((element) => {
|
||||
resp.records.forEach((element) => {
|
||||
for (const [key, value] of Object.entries(element)) {
|
||||
if (!result[key]) {
|
||||
result[key] = { type: value.type, value: [] };
|
||||
@@ -169,12 +138,143 @@ export class DataProcessingAction implements IAction {
|
||||
}
|
||||
});
|
||||
return result;
|
||||
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
type Resp = { records: RespRecordType[] };
|
||||
|
||||
type RespRecordType = {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
value: any;
|
||||
};
|
||||
};
|
||||
|
||||
type Result = {
|
||||
[key: string]: {
|
||||
type: string;
|
||||
value: any[];
|
||||
};
|
||||
};
|
||||
|
||||
type Var = {
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
const ERROR_TYPE = "ERROR_TYPE";
|
||||
|
||||
const calc = (field: Field, result: Result) => {
|
||||
const type = typeCheck(field.type);
|
||||
if (!type) {
|
||||
return ERROR_TYPE;
|
||||
}
|
||||
|
||||
const fun =
|
||||
calcFunc[`${type}_${Operator[field.operator as keyof typeof Operator]}`];
|
||||
if (!fun) {
|
||||
return ERROR_TYPE;
|
||||
}
|
||||
const values = result[field.code].value;
|
||||
if (!values) {
|
||||
return null;
|
||||
}
|
||||
return fun(values);
|
||||
};
|
||||
|
||||
const typeCheck = (type: string) => {
|
||||
switch (type) {
|
||||
case "RECORD_NUMBER":
|
||||
case "NUMBER":
|
||||
return CalcType.NUMBER;
|
||||
case "SINGLE_LINE_TEXT":
|
||||
case "MULTI_LINE_TEXT":
|
||||
case "RICH_TEXT":
|
||||
return CalcType.STRING;
|
||||
case "DATE":
|
||||
return CalcType.DATE;
|
||||
case "TIME":
|
||||
return CalcType.TIME;
|
||||
case "DATETIME":
|
||||
case "UPDATED_TIME":
|
||||
return CalcType.DATETIME;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
enum Operator {
|
||||
SUM = "SUM",
|
||||
AVG = "AVG",
|
||||
MAX = "MAX",
|
||||
MIN = "MIN",
|
||||
COUNT = "COUNT",
|
||||
FIRST = "FIRST"
|
||||
}
|
||||
|
||||
new DataProcessingAction();
|
||||
enum CalcType {
|
||||
NUMBER = "number",
|
||||
STRING = "string",
|
||||
DATE = "date",
|
||||
TIME = "time",
|
||||
DATETIME = "datetime",
|
||||
}
|
||||
|
||||
const calcFunc: Record<string, (value: string[]) => string | null> = {
|
||||
[`${CalcType.NUMBER}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
[`${CalcType.STRING}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
[`${CalcType.DATE}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
[`${CalcType.TIME}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
[`${CalcType.DATETIME}_${Operator.COUNT}`]: (value: string[]) =>
|
||||
value.length.toString(),
|
||||
|
||||
[`${CalcType.NUMBER}_${Operator.SUM}`]: (value: string[]) =>
|
||||
value.reduce((acc, v) => acc + Number(v), 0).toString(),
|
||||
[`${CalcType.NUMBER}_${Operator.AVG}`]: (value: string[]) =>
|
||||
(value.reduce((acc, v) => acc + Number(v), 0) / value.length).toString(),
|
||||
[`${CalcType.NUMBER}_${Operator.MAX}`]: (value: string[]) =>
|
||||
Math.max(...value.map(Number)).toString(),
|
||||
[`${CalcType.NUMBER}_${Operator.MIN}`]: (value: string[]) =>
|
||||
Math.min(...value.map(Number)).toString(),
|
||||
|
||||
[`${CalcType.STRING}_${Operator.SUM}`]: (value: string[]) => value.join(" "),
|
||||
|
||||
[`${CalcType.DATE}_${Operator.MAX}`]: (value: string[]) =>
|
||||
value.reduce((maxDate, currentDate) =>
|
||||
maxDate > currentDate ? maxDate : currentDate
|
||||
),
|
||||
|
||||
[`${CalcType.DATE}_${Operator.MIN}`]: (value: string[]) =>
|
||||
value.reduce((minDate, currentDate) =>
|
||||
minDate < currentDate ? minDate : currentDate
|
||||
),
|
||||
|
||||
[`${CalcType.TIME}_${Operator.MAX}`]: (value: string[]) =>
|
||||
value.reduce((maxTime, currentTime) =>
|
||||
maxTime > currentTime ? maxTime : currentTime
|
||||
),
|
||||
[`${CalcType.TIME}_${Operator.MIN}`]: (value: string[]) =>
|
||||
value.reduce((minTime, currentTime) =>
|
||||
minTime < currentTime ? minTime : currentTime
|
||||
),
|
||||
|
||||
[`${CalcType.DATETIME}_${Operator.MAX}`]: (value: string[]) =>
|
||||
value.reduce((maxDateTime, currentDateTime) =>
|
||||
new Date(maxDateTime) > new Date(currentDateTime)
|
||||
? maxDateTime
|
||||
: currentDateTime
|
||||
),
|
||||
|
||||
[`${CalcType.DATETIME}_${Operator.MIN}`]: (value: string[]) =>
|
||||
value.reduce((minDateTime, currentDateTime) =>
|
||||
new Date(minDateTime) < new Date(currentDateTime)
|
||||
? minDateTime
|
||||
: currentDateTime
|
||||
),
|
||||
[`${CalcType.STRING}_${Operator.FIRST}`]:(value: string[])=>{
|
||||
return value[0];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,334 +0,0 @@
|
||||
import {
|
||||
IAction,
|
||||
IActionResult,
|
||||
IActionNode,
|
||||
IActionProperty,
|
||||
IContext,
|
||||
} from "../types/ActionTypes";
|
||||
import { actionAddins } from ".";
|
||||
import { KintoneRestAPIClient } from "@kintone/rest-api-client";
|
||||
import { Lookup } from "@kintone/rest-api-client/lib/src/KintoneFields/types/property";
|
||||
import { FieldForm, FieldType } from "../types/FieldLayout";
|
||||
interface Props {
|
||||
displayName: string;
|
||||
sources: Sources;
|
||||
dataMapping: DataMapping;
|
||||
}
|
||||
|
||||
interface DataMapping {
|
||||
data: Mapping[];
|
||||
createWithNull: boolean;
|
||||
}
|
||||
|
||||
interface Mapping {
|
||||
id: string;
|
||||
from: From;
|
||||
to: To;
|
||||
isKey: boolean;
|
||||
}
|
||||
|
||||
|
||||
|
||||
interface To {
|
||||
app: App;
|
||||
fields:FieldForm[];
|
||||
isDialogVisible: boolean;
|
||||
}
|
||||
|
||||
|
||||
interface From {
|
||||
sharedText: string;
|
||||
id: string;
|
||||
objectType: 'variable'|'field'|'text';
|
||||
}
|
||||
|
||||
interface IVar extends From{
|
||||
name:{
|
||||
name:string;
|
||||
}
|
||||
}
|
||||
|
||||
interface IFromField extends From,FieldForm{
|
||||
|
||||
}
|
||||
interface Sources {
|
||||
app: App;
|
||||
}
|
||||
|
||||
interface App {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdate: string;
|
||||
}
|
||||
|
||||
export class DataUpdateAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
dataMappingProps: Props;
|
||||
constructor() {
|
||||
this.name = "データ更新";
|
||||
this.actionProps = [];
|
||||
this.dataMappingProps = {} as Props;
|
||||
this.register();
|
||||
}
|
||||
|
||||
async process(
|
||||
actionNode: IActionNode,
|
||||
event: any,
|
||||
context: IContext
|
||||
): Promise<IActionResult> {
|
||||
this.actionProps = actionNode.actionProps;
|
||||
this.dataMappingProps = actionNode.ActionValue as Props;
|
||||
console.log(context);
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: "",
|
||||
} as IActionResult;
|
||||
try {
|
||||
const lookupFixedFieldCodes = await getLookupFixedFieldCodes(
|
||||
this.dataMappingProps.sources.app.id
|
||||
);
|
||||
|
||||
// createWithNull が有効な場合は、4 番目のパラメーターを true にして doUpdate 関数ブランチを実行します。
|
||||
if (this.dataMappingProps.dataMapping.createWithNull) {
|
||||
await doUpdate(
|
||||
this.dataMappingProps.dataMapping.data,
|
||||
this.dataMappingProps.sources.app.id,
|
||||
context,
|
||||
true, // キーがない場合、またはキーでターゲットが見つからない場合に、マッピング条件によって新しいレコードを作成するかどうかを決定するために使用されます。
|
||||
lookupFixedFieldCodes
|
||||
);
|
||||
} else if (
|
||||
// キーがないと更新対象を取得できないため、この時点でのみ更新が行われます。 doUpdate 関数の 4 番目のパラメーターは false です。
|
||||
this.dataMappingProps.dataMapping.data
|
||||
.map((m) => m.isKey)
|
||||
.find((isKey) => isKey === true)
|
||||
) {
|
||||
await doUpdate(
|
||||
this.dataMappingProps.dataMapping.data,
|
||||
this.dataMappingProps.sources.app.id,
|
||||
context,
|
||||
false,
|
||||
lookupFixedFieldCodes
|
||||
);
|
||||
} else {
|
||||
await doCreate(
|
||||
this.dataMappingProps.dataMapping.data,
|
||||
this.dataMappingProps.sources.app.id,
|
||||
context,
|
||||
lookupFixedFieldCodes
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
context.errors.handleError(error,actionNode);
|
||||
result.canNext = false;
|
||||
}
|
||||
console.log("dataMappingProps", this.dataMappingProps);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
|
||||
new DataUpdateAction();
|
||||
|
||||
const getContextVarByPath = (obj: any, path: string) => {
|
||||
return path.split(".").reduce((o, k) => (o || {})[k], obj);
|
||||
};
|
||||
|
||||
interface UpdateRecord {
|
||||
id: string;
|
||||
record: {
|
||||
[key: string]: {
|
||||
value: any;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const client = new KintoneRestAPIClient();
|
||||
|
||||
const getFromValue=(item:Mapping,context:IContext)=>{
|
||||
if (item.from.objectType === "variable") {
|
||||
const rfrom =item.from as IVar;
|
||||
return getContextVarByPath(context.variables,rfrom.name.name);
|
||||
}else if(item.from.objectType === "field"){
|
||||
const field = item.from as IFromField;
|
||||
return context.record[field.code].value;
|
||||
}
|
||||
else {
|
||||
return item.from.sharedText;
|
||||
}
|
||||
}
|
||||
|
||||
const doUpdate = async (
|
||||
mappingData: Mapping[],
|
||||
appId: string,
|
||||
context: IContext,
|
||||
needCreate: boolean,
|
||||
lookupFixedFieldCodes: string[]
|
||||
) => {
|
||||
const targetField = await findUpdateField(mappingData, appId, context);
|
||||
console.log(targetField);
|
||||
if (targetField.records.length === 0 && needCreate) {
|
||||
await doCreate(mappingData, appId, context, lookupFixedFieldCodes);
|
||||
} else {
|
||||
// マッピングデータを単純なオブジェクトに処理し、ソース値が変数の場合は変数を置き換えます。
|
||||
const mappingRules = mappingData
|
||||
.filter(
|
||||
(m) =>
|
||||
Object.keys(m.from).length > 0 &&
|
||||
!lookupFixedFieldCodes.includes(m.to.fields[0].code)
|
||||
)
|
||||
.map((m) => {
|
||||
if (m.from.objectType === "variable") {
|
||||
const rfrom =m.from as IVar;
|
||||
return {
|
||||
value: getContextVarByPath(context.variables,rfrom.name.name),
|
||||
code: m.to.fields[0].code,
|
||||
};
|
||||
}else if(m.from.objectType === "field"){
|
||||
const field = m.from as IFromField;
|
||||
return {
|
||||
value: context.record[field.code].value,
|
||||
code: m.to.fields[0].code,
|
||||
}
|
||||
}
|
||||
else {
|
||||
return {
|
||||
value: m.from.sharedText,
|
||||
code: m.to.fields[0].code,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const updateRecords: UpdateRecord[] = targetField.records.map(
|
||||
(targetRecord) => {
|
||||
const updateRecord: UpdateRecord["record"] = {};
|
||||
|
||||
// マッピング内のルールにヒットしたフィールドのみが更新されます。
|
||||
for (const mapping of mappingRules) {
|
||||
if (targetRecord[mapping.code]) {
|
||||
updateRecord[mapping.code] = {
|
||||
value: mapping.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: targetRecord.$id.value as string,
|
||||
record: updateRecord,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
console.log(updateRecords);
|
||||
await client.record.updateRecords({
|
||||
app: appId,
|
||||
records: updateRecords,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const makeQuery=(field:FieldForm,key:any)=>{
|
||||
if(field.type===FieldType.NUMBER || field.type===FieldType.RECORD_NUMBER){
|
||||
return `${field.code} = ${Number(key)}`
|
||||
}
|
||||
if(typeof key==='string'){
|
||||
return `${field.code} = "${key}"`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const findUpdateField = async (
|
||||
mappingData: Mapping[],
|
||||
appId: string,
|
||||
context: IContext
|
||||
) => {
|
||||
const queryStr = mappingData
|
||||
.filter((m) => m.to.app && m.to.fields && m.to.fields.length > 0 && m.isKey)
|
||||
.map((m) => {
|
||||
if (m.from.objectType === "variable") {
|
||||
const vfrom = m.from as IVar;
|
||||
return makeQuery(m.to.fields[0],getContextVarByPath(context.variables , vfrom.name.name));
|
||||
}
|
||||
else if(m.from.objectType === "field"){
|
||||
const field = m.from as IFromField;
|
||||
return makeQuery(m.to.fields[0],context.record[field.code].value);
|
||||
}
|
||||
else{
|
||||
return makeQuery(m.to.fields[0],m.from.sharedText);
|
||||
}
|
||||
})
|
||||
.join("&");
|
||||
// 検索条件が空の場合は全レコードを返すため、検索対象が見つからない場合は検索は行われません。
|
||||
if (queryStr.length === 0) {
|
||||
return {
|
||||
records: [],
|
||||
};
|
||||
} else {
|
||||
return await client.record.getRecords({
|
||||
app: appId,
|
||||
// query: undefined
|
||||
query: queryStr,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const doCreate = async (
|
||||
mappingData: Mapping[],
|
||||
appId: string,
|
||||
context: IContext,
|
||||
lookupFixedFieldCodes: string[]
|
||||
) => {
|
||||
const filterHandler = (item:Mapping)=>{
|
||||
if(!item.to.fields || item.to.fields.length===0){
|
||||
return false;
|
||||
}
|
||||
if(item.from.objectType === "variable" && (item.from as IVar).name.name ){
|
||||
return true;
|
||||
}
|
||||
if(item.from.objectType === "field" && (item.from as IFromField).code){
|
||||
return true;
|
||||
}
|
||||
if(item.from.objectType === "text" && item.from.sharedText!==null){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
const record = mappingData
|
||||
.filter(filterHandler)
|
||||
.filter((item) => !lookupFixedFieldCodes.includes(item.to.fields[0].code))
|
||||
.reduce((accumulator, item) => {
|
||||
return {
|
||||
...accumulator,
|
||||
[item.to.fields[0].code]: {
|
||||
value: getFromValue(item,context),
|
||||
},
|
||||
};
|
||||
}, {});
|
||||
if (record && Object.keys(record).length > 0) {
|
||||
console.log(record);
|
||||
await client.record.addRecord({
|
||||
app:appId,
|
||||
record:record
|
||||
});
|
||||
// await kintone.api(kintone.api.url("/k/v1/record.json", true), "POST", {
|
||||
// app: appId,
|
||||
// record: record,
|
||||
// });
|
||||
}
|
||||
};
|
||||
|
||||
const getLookupFixedFieldCodes = async (appId: string) => {
|
||||
return await client.app
|
||||
.getFormFields({ app: appId })
|
||||
.then((resp) =>
|
||||
Object.values(resp.properties)
|
||||
.filter((f) => (f as Lookup).lookup !== undefined)
|
||||
.flatMap((f) => (f as Lookup).lookup.fieldMappings.map((m) => m.field))
|
||||
);
|
||||
};
|
||||
@@ -1,89 +0,0 @@
|
||||
import { actionAddins } from ".";
|
||||
import { IAction, IActionResult, IActionNode, IActionProperty, IField ,IContext, IVarName} from "../types/ActionTypes";
|
||||
/**
|
||||
* アクションの属性定義
|
||||
*/
|
||||
interface IDateSpecifiedProps {
|
||||
verNameGet:string;
|
||||
newYear:number;
|
||||
newMonth:number;
|
||||
newDay:number;
|
||||
verName:IVarName;
|
||||
}
|
||||
/**
|
||||
* 日付指定アクション
|
||||
*/
|
||||
export class DateSpecifiedAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
props: IDateSpecifiedProps;
|
||||
constructor() {
|
||||
this.name = "日付指定";
|
||||
this.actionProps = [];
|
||||
this.props = {
|
||||
verNameGet:'',
|
||||
newYear:0,
|
||||
newMonth:0,
|
||||
newDay:0,
|
||||
verName:{name:''}
|
||||
}
|
||||
this.register();
|
||||
}
|
||||
/**
|
||||
* アクションの実行を呼び出す
|
||||
* @param actionNode
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
async process(actionNode: IActionNode, event: any,context:IContext): Promise<IActionResult> {
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: false
|
||||
};
|
||||
try {
|
||||
//属性設定を取得する
|
||||
this.actionProps = actionNode.actionProps;
|
||||
if (!('verName' in actionNode.ActionValue) && !('verNameGet' in actionNode.ActionValue) ) {
|
||||
return result
|
||||
}
|
||||
this.props = actionNode.ActionValue as IDateSpecifiedProps;
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//本番コード開始:
|
||||
//取得変数の値を呼び出して代入する:
|
||||
const getContextVarByPath = (obj: any, path: string) => {
|
||||
return path.split(".").reduce((o, k) => (o || {})[k], obj);
|
||||
};
|
||||
let verNameGetValue = getContextVarByPath(context.variables,this.props.verNameGet);
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//取得変数の値Dateオブジェクトに変換:
|
||||
let dateObj = new Date(verNameGetValue);
|
||||
if(verNameGetValue === undefined || verNameGetValue === null || verNameGetValue === '' || isNaN(dateObj.getDate())){
|
||||
throw new Error("Invalid time value");
|
||||
}
|
||||
// 年の設定(newYearが設定されていない場合は、元の値を使用)
|
||||
dateObj.setFullYear(this.props.newYear >=1900 && this.props.newYear <=9999 ? this.props.newYear : dateObj.getFullYear());
|
||||
// 月の設定(newMonthが設定されていない場合は、元の値を使用)// 月は0始まりなので、12月は11。
|
||||
dateObj.setMonth(this.props.newMonth >=1 && this.props.newMonth <=12 ? this.props.newMonth-1 : dateObj.getMonth());
|
||||
// 日の設定(newDayが設定されていない場合は、元の値を使用)
|
||||
dateObj.setDate(this.props.newDay >=1 && this.props.newDay <=31 ?this.props.newDay : dateObj.getDate());
|
||||
// 変数に新しい値を設定
|
||||
if(this.props.verName && this.props.verName.name!==''){
|
||||
context.variables[this.props.verName.name]=dateObj.toISOString();
|
||||
}
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
result = {
|
||||
canNext:true,
|
||||
result:true
|
||||
}
|
||||
return result;
|
||||
}catch(error){
|
||||
context.errors.handleError(error,actionNode);
|
||||
result.canNext=false;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
}
|
||||
new DateSpecifiedAction();
|
||||
@@ -1,202 +0,0 @@
|
||||
import { actionAddins } from ".";
|
||||
import { IAction, IActionResult, IActionNode, IActionProperty, IField ,IContext, IVarName} from "../types/ActionTypes";
|
||||
|
||||
/**
|
||||
* アクションの属性定義
|
||||
*/
|
||||
interface IDateTimeCalcProps{
|
||||
verNameGet:string;
|
||||
calcOption:string;
|
||||
verName:IVarName;
|
||||
year:string;
|
||||
month:string;
|
||||
date:string;
|
||||
hour:string;
|
||||
minute:string;
|
||||
second:string;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export class DateTimeCalcAction implements IAction{
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
props:IDateTimeCalcProps;
|
||||
constructor(){
|
||||
this.name="日時を加算/減算する";// DBに登録したアクション名
|
||||
this.actionProps=[];
|
||||
//プロパティ属性の初期化
|
||||
this.props={
|
||||
verNameGet:'',
|
||||
calcOption:'',
|
||||
verName:{name:''},
|
||||
year:"0",
|
||||
month:"0",
|
||||
date:"0",
|
||||
hour:"0",
|
||||
minute:"0",
|
||||
second:"0"
|
||||
}
|
||||
//アクションを登録する
|
||||
this.register();
|
||||
}
|
||||
|
||||
/**
|
||||
* 基準日となる変数の値が、日付・日時の形式であるか、判断する
|
||||
* @param {string} dateValue
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isDateValue(dateValue :string){
|
||||
let date;
|
||||
|
||||
//正規表現チェック
|
||||
let singleDigitMonth = dateValue.match(/(\d{4})-(\d{1})-(\d{1})$/);//4桁の数字-1桁の数字-2桁の数字
|
||||
let twoDigitMonth = dateValue.match(/(\d{4})-(\d{2})-(\d{2})$/);//4桁の数字-2桁の数字-2桁の数字
|
||||
let singleDigitDate = dateValue.match(/(\d{4})-(\d{2})-(\d{1})$/);//4桁の数字-2桁の数字-1桁の数字
|
||||
let twoDigitDate = dateValue.match(/(\d{4})-(\d{1})-(\d{2})$/);//4桁の数字-1桁の数字-2桁の数字
|
||||
let dateTimeMilliSecond = dateValue.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).(\d{2,3})Z$/);//時刻入りのUTCの日付形式(ミリ秒)
|
||||
let dateTime = dateValue.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$/);//時刻入りのUTCの日付形式
|
||||
|
||||
//date型に変換
|
||||
date = new Date(dateValue);
|
||||
|
||||
//date型変換できたか確認
|
||||
if(date !== undefined && !isNaN(date.getDate())){
|
||||
//正規表現チェック確認
|
||||
if(twoDigitMonth === null && singleDigitMonth === null && singleDigitDate === null && twoDigitDate === null && dateTime === null && dateTimeMilliSecond === null){
|
||||
throw new Error("計算の基準日となる値が、適切な日付・日時の形式ではありません。「日時を加算/減算する」コンポーネントの処理を中断しました。");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 値を数値に変換する
|
||||
* @param {any} context
|
||||
* @param {string} calcValue,calcOption
|
||||
* @returns {number}
|
||||
*/
|
||||
valueToNumber(context :any,calcValue :string,calcOption :string): number{
|
||||
|
||||
const getContextVarByPath = (obj: any, path: string) => {
|
||||
return path.split(".").reduce((o, k) => (o || {})[k], obj);
|
||||
};
|
||||
|
||||
//計算値が変数の場合は、変数の値を取得
|
||||
if(calcOption === "変数" && isNaN(Number(calcValue))){
|
||||
calcValue = getContextVarByPath (context.variables,calcValue);
|
||||
}
|
||||
|
||||
//数値型に変換
|
||||
let number = Number(calcValue);
|
||||
|
||||
//有限数かどうか判定
|
||||
if(!isFinite(number)){
|
||||
throw new Error("計算値が、数値ではありません。「日時を加算/減算する」コンポーネントの処理を中断しました。");
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 日付・日時を加算・減算する
|
||||
* @param {any} dateValue
|
||||
* @param {number} year month day hour minute second
|
||||
* @returns {string}
|
||||
*/
|
||||
calcDate(dateValue:any,year:number,month:number,date:number,hour:number,minute:number,second:number):string{
|
||||
|
||||
let calcResult;
|
||||
//フィールドの値(文字列)をdate型に変換する
|
||||
dateValue = new Date(dateValue);
|
||||
|
||||
// 年を計算
|
||||
dateValue.setFullYear(dateValue.getFullYear()+year);
|
||||
//月を計算
|
||||
dateValue.setMonth(dateValue.getMonth()+month);
|
||||
//日を計算
|
||||
dateValue.setDate(dateValue.getDate()+date);
|
||||
//時間を計算
|
||||
dateValue.setHours(dateValue.getHours()+hour);
|
||||
//分を計算
|
||||
dateValue.setMinutes(dateValue.getMinutes()+minute);
|
||||
//秒を計算
|
||||
dateValue.setSeconds(dateValue.getSeconds()+second);
|
||||
|
||||
//UTC形式に変換
|
||||
calcResult = dateValue.toISOString();
|
||||
return calcResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* アクションの実行を呼び出す
|
||||
* @param actionNode
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
async process(actionNode:IActionNode,event:any,context:IContext):Promise<IActionResult> {
|
||||
let result={
|
||||
canNext:true,
|
||||
result:false
|
||||
};
|
||||
try{
|
||||
//属性設定を取得する
|
||||
this.actionProps = actionNode.actionProps;
|
||||
this.props = actionNode.ActionValue as IDateTimeCalcProps;
|
||||
|
||||
const getContextVarByPath = (obj: any, path: string) => {
|
||||
return path.split(".").reduce((o, k) => (o || {})[k], obj);
|
||||
};
|
||||
|
||||
//基準日となる変数の値取得
|
||||
const dateValue = getContextVarByPath (context.variables,this.props.verNameGet);
|
||||
|
||||
//基準日となる変数の値が空の場合、処理を終了する
|
||||
if(!dateValue){
|
||||
throw new Error("基準値となる変数の値が空、または存在しません。「日時を加算/減算する」コンポーネントの処理を中断しました。");
|
||||
}
|
||||
|
||||
let checkDateValue;
|
||||
//基準値となる変数の値、日時、日付形式か確認する
|
||||
checkDateValue = this.isDateValue(dateValue);
|
||||
|
||||
if(checkDateValue){
|
||||
//計算値の入力方法を取得する
|
||||
let calcOptions = this.props.calcOption;
|
||||
|
||||
//計算値を数値型に変換する
|
||||
let year = this.valueToNumber(context,this.props.year,calcOptions);
|
||||
let month = this.valueToNumber(context,this.props.month,calcOptions);
|
||||
let date = this.valueToNumber(context,this.props.date,calcOptions);
|
||||
let hour = this.valueToNumber(context,this.props.hour,calcOptions);
|
||||
let minute = this.valueToNumber(context,this.props.minute,calcOptions);
|
||||
let second = this.valueToNumber(context,this.props.second,calcOptions);
|
||||
|
||||
//計算結果の日付を格納する変数
|
||||
let calculatedDate;
|
||||
|
||||
//日付を加算、減算する
|
||||
calculatedDate = this.calcDate(dateValue,year,month,date,hour,minute,second);
|
||||
|
||||
//計算結果を変数に代入する
|
||||
context.variables[this.props.verName.name] = calculatedDate;
|
||||
}
|
||||
|
||||
result= {
|
||||
canNext:true,
|
||||
result:true
|
||||
}
|
||||
return result;
|
||||
}catch(error){
|
||||
context.errors.handleError(error,actionNode);
|
||||
result.canNext=false;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
register(): void {
|
||||
actionAddins[this.name]=this;
|
||||
}
|
||||
}
|
||||
new DateTimeCalcAction();
|
||||
@@ -1,65 +0,0 @@
|
||||
import { actionAddins } from ".";
|
||||
import { IAction, IActionResult, IActionNode, IActionProperty, IField ,IContext, IVarName} from "../types/ActionTypes";
|
||||
/**
|
||||
* アクションの属性定義
|
||||
*/
|
||||
interface IDatetimeGetterProps {
|
||||
/**変数の名前 */
|
||||
verName:IVarName;
|
||||
}
|
||||
/**
|
||||
* 現在日時を取得するアクション
|
||||
*/
|
||||
export class DatetimeGetterAction implements IAction {
|
||||
name: string;
|
||||
actionProps: IActionProperty[];
|
||||
props: IDatetimeGetterProps;
|
||||
constructor() {
|
||||
this.name = "現在日時";
|
||||
this.actionProps = [];
|
||||
this.props = {
|
||||
verName:{name:''}
|
||||
}
|
||||
this.register();
|
||||
}
|
||||
/**
|
||||
* アクションの実行を呼び出す
|
||||
* @param actionNode
|
||||
* @param event
|
||||
* @returns
|
||||
*/
|
||||
async process(actionNode: IActionNode, event: any,context:IContext): Promise<IActionResult> {
|
||||
let result = {
|
||||
canNext: true,
|
||||
result: false
|
||||
};
|
||||
|
||||
try {
|
||||
//属性設定を取得する
|
||||
this.actionProps = actionNode.actionProps;
|
||||
|
||||
if (!('verName' in actionNode.ActionValue) ) {
|
||||
return result
|
||||
}
|
||||
this.props = actionNode.ActionValue as IDatetimeGetterProps;
|
||||
|
||||
let today = new Date();
|
||||
|
||||
if(this.props.verName && this.props.verName.name!==''){
|
||||
context.variables[this.props.verName.name]=today.toISOString();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
context.errors.handleError(error,actionNode);
|
||||
result.canNext = false;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
register(): void {
|
||||
actionAddins[this.name] = this;
|
||||
}
|
||||
|
||||
}
|
||||
new DatetimeGetterAction();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user